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    #[cfg(feature = "jit")]
1010    if crate::vm::jit::try_call(&function, argument_count, context) {
1011        return Ok(CallValue::Complete);
1012    }
1013
1014    let code = function.code.clone();
1015    let environments = function.environments.clone();
1016    let script_or_module = function.script_or_module.clone();
1017
1018    drop(function);
1019
1020    let env_fp = environments.len() as u32;
1021
1022    let frame = CallFrame::new(code, script_or_module, environments, realm)
1023        .with_argument_count(argument_count as u32)
1024        .with_env_fp(env_fp);
1025
1026    #[cfg(feature = "native-backtrace")]
1027    {
1028        let native_source_info = context.native_source_info();
1029        context
1030            .vm
1031            .shadow_stack
1032            .patch_last_native(native_source_info);
1033    }
1034
1035    context.vm.push_frame(frame);
1036    context.vm.set_return_value(JsValue::undefined());
1037
1038    let context = context.context();
1039
1040    let lexical_this_mode = context.vm.frame().code_block.this_mode == ThisMode::Lexical;
1041    let this = if lexical_this_mode {
1042        ThisBindingStatus::Lexical
1043    } else {
1044        let this = context.vm.stack.get_this(context.vm.frame());
1045        if context.vm.frame().code_block.strict() {
1046            context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED;
1047            ThisBindingStatus::Initialized(this)
1048        } else if this.is_null_or_undefined() {
1049            context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED;
1050            let this: JsValue = context.realm().global_this().clone().into();
1051            context.vm.stack.set_this(
1052                context.vm.frames.last().js_expect("frame must exist")?,
1053                this.clone(),
1054            );
1055            ThisBindingStatus::Initialized(this)
1056        } else {
1057            let this: JsValue = this
1058                .to_object(context)
1059                .js_expect("conversion cannot fail")?
1060                .into();
1061            context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED;
1062            context.vm.stack.set_this(
1063                context.vm.frames.last().js_expect("frame must exist")?,
1064                this.clone(),
1065            );
1066            ThisBindingStatus::Initialized(this)
1067        }
1068    };
1069
1070    let mut last_env = 0;
1071
1072    let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier();
1073    let has_function_scope = context.vm.frame().code_block().has_function_scope();
1074
1075    if has_binding_identifier {
1076        let frame = context.vm.frame_mut();
1077        let global = frame.realm.environment();
1078        let index = frame.environments.push_lexical(1, global);
1079        frame.environments.put_lexical_value(
1080            BindingLocatorScope::Stack(index),
1081            0,
1082            function_object.clone().into(),
1083            global,
1084        );
1085        last_env += 1;
1086    }
1087
1088    if has_function_scope {
1089        let scope = context.vm.frame().code_block().constant_scope(last_env);
1090        let frame = context.vm.frame_mut();
1091        let global = frame.realm.environment();
1092        frame.environments.push_function(
1093            scope,
1094            FunctionSlots::new(this, function_object.clone(), None),
1095            global,
1096        );
1097    }
1098
1099    Ok(CallValue::Ready)
1100}
1101
1102/// Construct an instance of this object with the specified arguments.
1103///
1104/// # Panics
1105///
1106/// Panics if the object is currently mutably borrowed.
1107// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
1108fn function_construct(
1109    this_function_object: &JsObject,
1110    argument_count: usize,
1111    context: &mut InternalMethodCallContext<'_>,
1112) -> JsResult<CallValue> {
1113    context.check_runtime_limits()?;
1114
1115    let function = this_function_object
1116        .downcast_ref::<OrdinaryFunction>()
1117        .js_expect("not a function")?;
1118    let realm = function.realm().clone();
1119
1120    debug_assert!(
1121        function.is_ordinary(),
1122        "only ordinary functions can be constructed"
1123    );
1124
1125    let code = function.code.clone();
1126    let environments = function.environments.clone();
1127    let script_or_module = function.script_or_module.clone();
1128    drop(function);
1129
1130    let env_fp = environments.len() as u32;
1131
1132    let new_target = context.vm.stack.pop();
1133
1134    let this = if code.is_derived_constructor() {
1135        None
1136    } else {
1137        // If the prototype of the constructor is not an object, then use the default object
1138        // prototype as prototype for the new object
1139        // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
1140        // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
1141        let prototype =
1142            get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?;
1143        let this = JsObject::from_proto_and_data_with_shared_shape(
1144            context.root_shape(),
1145            prototype,
1146            OrdinaryObject,
1147        )
1148        .upcast();
1149
1150        this.initialize_instance_elements(this_function_object, context)?;
1151
1152        Some(this)
1153    };
1154
1155    let mut frame = CallFrame::new(code, script_or_module, environments, realm)
1156        .with_argument_count(argument_count as u32)
1157        .with_env_fp(env_fp)
1158        .with_flags(CallFrameFlags::CONSTRUCT);
1159
1160    // We push the `this` below so we can mark this function as having the this value
1161    // cached if it's initialized.
1162    frame
1163        .flags
1164        .set(CallFrameFlags::THIS_VALUE_CACHED, this.is_some());
1165
1166    #[cfg(feature = "native-backtrace")]
1167    {
1168        let native_source_info = context.native_source_info();
1169        context
1170            .vm
1171            .shadow_stack
1172            .patch_last_native(native_source_info);
1173    }
1174
1175    context.vm.push_frame(frame);
1176    context.vm.set_return_value(JsValue::undefined());
1177
1178    let mut last_env = 0;
1179
1180    let has_binding_identifier = context.vm.frame().code_block().has_binding_identifier();
1181    let has_function_scope = context.vm.frame().code_block().has_function_scope();
1182
1183    if has_binding_identifier {
1184        let frame = context.vm.frame_mut();
1185        let global = frame.realm.environment();
1186        let index = frame.environments.push_lexical(1, global);
1187        frame.environments.put_lexical_value(
1188            BindingLocatorScope::Stack(index),
1189            0,
1190            this_function_object.clone().into(),
1191            global,
1192        );
1193        last_env += 1;
1194    }
1195
1196    if has_function_scope {
1197        let scope = context.vm.frame().code_block().constant_scope(last_env);
1198        let frame = context.vm.frame_mut();
1199        let global = frame.realm.environment();
1200        frame.environments.push_function(
1201            scope,
1202            FunctionSlots::new(
1203                this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
1204                    ThisBindingStatus::Initialized(o.into())
1205                }),
1206                this_function_object.clone(),
1207                Some(
1208                    new_target
1209                        .as_object()
1210                        .js_expect("new.target should be an object")?
1211                        .clone(),
1212                ),
1213            ),
1214            global,
1215        );
1216    }
1217
1218    let context = context.context();
1219    context.vm.stack.set_this(
1220        context.vm.frames.last().js_expect("frame must exist")?,
1221        this.map(JsValue::new).unwrap_or_default(),
1222    );
1223
1224    Ok(CallValue::Ready)
1225}