mirror of https://github.com/boa-dev/boa.git
Browse Source
This Pull Request fixes/closes #1559. It changes the following: - Implement GeneratorFunction Objects - Implement Generator Objects - Implement generator execution in vm - Create `FormalParameterList` to remove duplicate checks on function parameters - Refactor `MethodDefinition` on object literalspull/1870/head
raskad
3 years ago
47 changed files with 2303 additions and 941 deletions
@ -0,0 +1,415 @@
|
||||
//! This module implements the global `Generator` object.
|
||||
//!
|
||||
//! A Generator is an instance of a generator function and conforms to both the Iterator and Iterable interfaces.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-generator-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator
|
||||
|
||||
use crate::{ |
||||
builtins::{iterable::create_iter_result_object, BuiltIn, JsArgs}, |
||||
environments::DeclarativeEnvironmentStack, |
||||
object::{ConstructorBuilder, JsObject, ObjectData}, |
||||
property::{Attribute, PropertyDescriptor}, |
||||
symbol::WellKnownSymbols, |
||||
value::JsValue, |
||||
vm::{CallFrame, GeneratorResumeKind, ReturnType}, |
||||
Context, JsResult, |
||||
}; |
||||
use boa_gc::{Cell, Finalize, Gc, Trace}; |
||||
use boa_profiler::Profiler; |
||||
|
||||
/// Indicates the state of a generator.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)] |
||||
pub(crate) enum GeneratorState { |
||||
Undefined, |
||||
SuspendedStart, |
||||
SuspendedYield, |
||||
Executing, |
||||
Completed, |
||||
} |
||||
|
||||
/// Holds all information that a generator needs to continue it's execution.
|
||||
///
|
||||
/// All of the fields must be changed with those that are currently present in the
|
||||
/// context/vm before the generator execution starts/resumes and after it has ended/yielded.
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub(crate) struct GeneratorContext { |
||||
pub(crate) environments: DeclarativeEnvironmentStack, |
||||
pub(crate) call_frame: CallFrame, |
||||
pub(crate) stack: Vec<JsValue>, |
||||
} |
||||
|
||||
/// The internal representation on a `Generator` object.
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub struct Generator { |
||||
/// The `[[GeneratorState]]` internal slot.
|
||||
#[unsafe_ignore_trace] |
||||
pub(crate) state: GeneratorState, |
||||
|
||||
/// The `[[GeneratorContext]]` internal slot.
|
||||
pub(crate) context: Option<Gc<Cell<GeneratorContext>>>, |
||||
} |
||||
|
||||
impl BuiltIn for Generator { |
||||
const NAME: &'static str = "Generator"; |
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::NON_ENUMERABLE.union(Attribute::CONFIGURABLE); |
||||
|
||||
fn init(context: &mut Context) -> JsValue { |
||||
let _timer = Profiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
let iterator_prototype = context.iterator_prototypes().iterator_prototype(); |
||||
|
||||
let generator_function_prototype = context |
||||
.standard_objects() |
||||
.generator_function_object() |
||||
.prototype(); |
||||
|
||||
let obj = ConstructorBuilder::with_standard_object( |
||||
context, |
||||
Self::constructor, |
||||
context.standard_objects().generator_object().clone(), |
||||
) |
||||
.name(Self::NAME) |
||||
.length(Self::LENGTH) |
||||
.property( |
||||
WellKnownSymbols::to_string_tag(), |
||||
Self::NAME, |
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
||||
) |
||||
.method(Self::next, "next", 1) |
||||
.method(Self::r#return, "return", 1) |
||||
.method(Self::throw, "throw", 1) |
||||
.inherit(iterator_prototype) |
||||
.build(); |
||||
|
||||
context |
||||
.standard_objects() |
||||
.generator_object() |
||||
.prototype |
||||
.insert_property( |
||||
"constructor", |
||||
PropertyDescriptor::builder() |
||||
.value(generator_function_prototype) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true), |
||||
); |
||||
|
||||
obj.into() |
||||
} |
||||
} |
||||
|
||||
impl Generator { |
||||
pub(crate) const LENGTH: usize = 0; |
||||
|
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn constructor( |
||||
_: &JsValue, |
||||
_: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
let prototype = context.standard_objects().generator_object().prototype(); |
||||
|
||||
let this = JsObject::from_proto_and_data( |
||||
prototype, |
||||
ObjectData::generator(Self { |
||||
state: GeneratorState::Undefined, |
||||
context: None, |
||||
}), |
||||
); |
||||
|
||||
Ok(this.into()) |
||||
} |
||||
|
||||
/// `Generator.prototype.next ( value )`
|
||||
///
|
||||
/// The `next()` method returns an object with two properties done and value.
|
||||
/// You can also provide a parameter to the next method to send a value to the generator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.next
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next
|
||||
pub(crate) fn next( |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Return ? GeneratorResume(this value, value, empty).
|
||||
match this.as_object() { |
||||
Some(obj) if obj.is_generator() => { |
||||
Self::generator_resume(obj, args.get_or_undefined(0), context) |
||||
} |
||||
_ => context.throw_type_error("Generator.prototype.next called on non generator"), |
||||
} |
||||
} |
||||
|
||||
/// `Generator.prototype.return ( value )`
|
||||
///
|
||||
/// The `return()` method returns the given value and finishes the generator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.return
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return
|
||||
pub(crate) fn r#return( |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let g be the this value.
|
||||
// 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) |
||||
} |
||||
|
||||
/// `Generator.prototype.throw ( exception )`
|
||||
///
|
||||
/// The `throw()` method resumes the execution of a generator by throwing an error into it
|
||||
/// and returns an object with two properties done and value.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.throw
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw
|
||||
pub(crate) fn throw( |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let g be the this value.
|
||||
// 2. Let C be ThrowCompletion(exception).
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) |
||||
} |
||||
|
||||
/// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorresume
|
||||
pub(crate) fn generator_resume( |
||||
generator_obj: &JsObject, |
||||
value: &JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let state be ? GeneratorValidate(generator, generatorBrand).
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut(); |
||||
let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
})?; |
||||
let state = generator.state; |
||||
|
||||
if state == GeneratorState::Executing { |
||||
return Err(context.construct_type_error("Generator should not be executing")); |
||||
} |
||||
|
||||
// 2. If state is completed, return CreateIterResultObject(undefined, true).
|
||||
if state == GeneratorState::Completed { |
||||
return Ok(create_iter_result_object( |
||||
JsValue::undefined(), |
||||
true, |
||||
context, |
||||
)); |
||||
} |
||||
|
||||
// 3. Assert: state is either suspendedStart or suspendedYield.
|
||||
assert!(matches!( |
||||
state, |
||||
GeneratorState::SuspendedStart | GeneratorState::SuspendedYield |
||||
)); |
||||
|
||||
// 4. Let genContext be generator.[[GeneratorContext]].
|
||||
// 5. Let methodContext be the running execution context.
|
||||
// 6. Suspend methodContext.
|
||||
// 7. Set generator.[[GeneratorState]] to executing.
|
||||
generator.state = GeneratorState::Executing; |
||||
let first_execution = matches!(state, GeneratorState::SuspendedStart); |
||||
|
||||
let generator_context_cell = generator |
||||
.context |
||||
.take() |
||||
.expect("generator context cannot be empty here"); |
||||
let mut generator_context = generator_context_cell.borrow_mut(); |
||||
drop(generator_obj_mut); |
||||
|
||||
std::mem::swap( |
||||
&mut context.realm.environments, |
||||
&mut generator_context.environments, |
||||
); |
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); |
||||
context.vm.push_frame(generator_context.call_frame.clone()); |
||||
if !first_execution { |
||||
context.vm.push(value); |
||||
} |
||||
|
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; |
||||
|
||||
let result = context.run(); |
||||
|
||||
generator_context.call_frame = *context |
||||
.vm |
||||
.pop_frame() |
||||
.expect("generator call frame must exist"); |
||||
std::mem::swap( |
||||
&mut context.realm.environments, |
||||
&mut generator_context.environments, |
||||
); |
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); |
||||
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut(); |
||||
let generator = generator_obj_mut |
||||
.as_generator_mut() |
||||
.expect("already checked this object type"); |
||||
|
||||
match result { |
||||
Ok((value, ReturnType::Yield)) => { |
||||
generator.state = GeneratorState::SuspendedYield; |
||||
drop(generator_context); |
||||
generator.context = Some(generator_context_cell); |
||||
Ok(create_iter_result_object(value, false, context)) |
||||
} |
||||
Ok((value, _)) => { |
||||
generator.state = GeneratorState::Completed; |
||||
Ok(create_iter_result_object(value, true, context)) |
||||
} |
||||
Err(value) => { |
||||
generator.state = GeneratorState::Completed; |
||||
Err(value) |
||||
} |
||||
} |
||||
|
||||
// 8. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
// 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation.
|
||||
// 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
|
||||
// 11. Return Completion(result).
|
||||
} |
||||
|
||||
/// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt
|
||||
pub(crate) fn generator_resume_abrupt( |
||||
this: &JsValue, |
||||
abrupt_completion: JsResult<JsValue>, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let state be ? GeneratorValidate(generator, generatorBrand).
|
||||
let generator_obj = this.as_object().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
})?; |
||||
let mut generator_obj_mut = generator_obj.borrow_mut(); |
||||
let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
})?; |
||||
let mut state = generator.state; |
||||
|
||||
if state == GeneratorState::Executing { |
||||
return Err(context.construct_type_error("Generator should not be executing")); |
||||
} |
||||
|
||||
// 2. If state is suspendedStart, then
|
||||
if state == GeneratorState::SuspendedStart { |
||||
// a. Set generator.[[GeneratorState]] to completed.
|
||||
generator.state = GeneratorState::Completed; |
||||
// b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point.
|
||||
generator.context = None; |
||||
// c. Set state to completed.
|
||||
state = GeneratorState::Completed; |
||||
} |
||||
|
||||
// 3. If state is completed, then
|
||||
if state == GeneratorState::Completed { |
||||
// a. If abruptCompletion.[[Type]] is return, then
|
||||
if let Ok(value) = abrupt_completion { |
||||
// i. Return CreateIterResultObject(abruptCompletion.[[Value]], true).
|
||||
return Ok(create_iter_result_object(value, true, context)); |
||||
} |
||||
// b. Return Completion(abruptCompletion).
|
||||
return abrupt_completion; |
||||
} |
||||
|
||||
// 4. Assert: state is suspendedYield.
|
||||
// 5. Let genContext be generator.[[GeneratorContext]].
|
||||
// 6. Let methodContext be the running execution context.
|
||||
// 7. Suspend methodContext.
|
||||
// 8. Set generator.[[GeneratorState]] to executing.
|
||||
// 9. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
// 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation.
|
||||
// 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context.
|
||||
// 12. Return Completion(result).
|
||||
let generator_context_cell = generator |
||||
.context |
||||
.take() |
||||
.expect("generator context cannot be empty here"); |
||||
let mut generator_context = generator_context_cell.borrow_mut(); |
||||
|
||||
generator.state = GeneratorState::Executing; |
||||
drop(generator_obj_mut); |
||||
|
||||
std::mem::swap( |
||||
&mut context.realm.environments, |
||||
&mut generator_context.environments, |
||||
); |
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); |
||||
context.vm.push_frame(generator_context.call_frame.clone()); |
||||
|
||||
let result = match abrupt_completion { |
||||
Ok(value) => { |
||||
context.vm.push(value); |
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; |
||||
context.run() |
||||
} |
||||
Err(value) => { |
||||
context.vm.push(value); |
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; |
||||
context.run() |
||||
} |
||||
}; |
||||
generator_context.call_frame = *context |
||||
.vm |
||||
.pop_frame() |
||||
.expect("generator call frame must exist"); |
||||
std::mem::swap( |
||||
&mut context.realm.environments, |
||||
&mut generator_context.environments, |
||||
); |
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); |
||||
|
||||
let mut generator_obj_mut = generator_obj.borrow_mut(); |
||||
let generator = generator_obj_mut |
||||
.as_generator_mut() |
||||
.expect("already checked this object type"); |
||||
|
||||
match result { |
||||
Ok((value, ReturnType::Yield)) => { |
||||
generator.state = GeneratorState::SuspendedYield; |
||||
drop(generator_context); |
||||
generator.context = Some(generator_context_cell); |
||||
Ok(create_iter_result_object(value, false, context)) |
||||
} |
||||
Ok((value, _)) => { |
||||
generator.state = GeneratorState::Completed; |
||||
Ok(create_iter_result_object(value, true, context)) |
||||
} |
||||
Err(value) => { |
||||
generator.state = GeneratorState::Completed; |
||||
Err(value) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,131 @@
|
||||
//! This module implements the global `GeneratorFunction` object.
|
||||
//!
|
||||
//! The `GeneratorFunction` constructor creates a new generator function object.
|
||||
//! In JavaScript, every generator function is actually a `GeneratorFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-generatorfunction-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction
|
||||
|
||||
use crate::{ |
||||
builtins::{function::Function, BuiltIn}, |
||||
context::StandardObjects, |
||||
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, |
||||
property::{Attribute, PropertyDescriptor}, |
||||
symbol::WellKnownSymbols, |
||||
value::JsValue, |
||||
Context, JsResult, |
||||
}; |
||||
use boa_profiler::Profiler; |
||||
|
||||
/// The internal representation on a `Generator` object.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub struct GeneratorFunction; |
||||
|
||||
impl BuiltIn for GeneratorFunction { |
||||
const NAME: &'static str = "GeneratorFunction"; |
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::NON_ENUMERABLE.union(Attribute::WRITABLE); |
||||
|
||||
fn init(context: &mut Context) -> JsValue { |
||||
let _timer = Profiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
let prototype = &context |
||||
.standard_objects() |
||||
.generator_function_object() |
||||
.prototype; |
||||
let constructor = &context |
||||
.standard_objects() |
||||
.generator_function_object() |
||||
.constructor; |
||||
|
||||
constructor.set_prototype(Some( |
||||
context.standard_objects().function_object().constructor(), |
||||
)); |
||||
let property = PropertyDescriptor::builder() |
||||
.value(1) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
constructor.borrow_mut().insert("length", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value("GeneratorFunction") |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
constructor.borrow_mut().insert("name", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value( |
||||
context |
||||
.standard_objects() |
||||
.generator_function_object() |
||||
.prototype(), |
||||
) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(false); |
||||
constructor.borrow_mut().insert("prototype", property); |
||||
constructor.borrow_mut().data = ObjectData::function(Function::Native { |
||||
function: Self::constructor, |
||||
constructor: true, |
||||
}); |
||||
|
||||
prototype.set_prototype(Some( |
||||
context.standard_objects().function_object().prototype(), |
||||
)); |
||||
let property = PropertyDescriptor::builder() |
||||
.value( |
||||
context |
||||
.standard_objects() |
||||
.generator_function_object() |
||||
.constructor(), |
||||
) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
prototype.borrow_mut().insert("constructor", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value(context.standard_objects().generator_object().prototype()) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
prototype.borrow_mut().insert("prototype", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value("GeneratorFunction") |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
prototype |
||||
.borrow_mut() |
||||
.insert(WellKnownSymbols::to_string_tag(), property); |
||||
|
||||
JsValue::Null |
||||
} |
||||
} |
||||
|
||||
impl GeneratorFunction { |
||||
pub(crate) fn constructor( |
||||
new_target: &JsValue, |
||||
_: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
let prototype = get_prototype_from_constructor( |
||||
new_target, |
||||
StandardObjects::generator_function_object, |
||||
context, |
||||
)?; |
||||
|
||||
let this = JsObject::from_proto_and_data( |
||||
prototype, |
||||
ObjectData::function(Function::Native { |
||||
function: |_, _, _| Ok(JsValue::undefined()), |
||||
constructor: true, |
||||
}), |
||||
); |
||||
|
||||
Ok(this.into()) |
||||
} |
||||
} |
@ -0,0 +1,153 @@
|
||||
use super::{Declaration, DeclarationPattern, Node}; |
||||
use bitflags::bitflags; |
||||
use boa_gc::{Finalize, Trace}; |
||||
use boa_interner::{Interner, Sym, ToInternedString}; |
||||
|
||||
#[cfg(feature = "deser")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// `FormalParameterList` is a list of `FormalParameter`s that describes the parameters of a function.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList
|
||||
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Default, PartialEq, Trace, Finalize)] |
||||
pub struct FormalParameterList { |
||||
pub(crate) parameters: Box<[FormalParameter]>, |
||||
#[unsafe_ignore_trace] |
||||
pub(crate) flags: FormalParameterListFlags, |
||||
} |
||||
|
||||
impl FormalParameterList { |
||||
/// Creates a new formal parameter list.
|
||||
pub(crate) fn new(parameters: Box<[FormalParameter]>, flags: FormalParameterListFlags) -> Self { |
||||
Self { parameters, flags } |
||||
} |
||||
|
||||
/// Indicates if the parameter list is simple.
|
||||
pub(crate) fn is_simple(&self) -> bool { |
||||
self.flags.contains(FormalParameterListFlags::IS_SIMPLE) |
||||
} |
||||
|
||||
/// Indicates if the parameter list has duplicate parameters.
|
||||
pub(crate) fn has_duplicates(&self) -> bool { |
||||
self.flags |
||||
.contains(FormalParameterListFlags::HAS_DUPLICATES) |
||||
} |
||||
|
||||
/// Indicates if the parameter list has a rest parameter.
|
||||
pub(crate) fn has_rest_parameter(&self) -> bool { |
||||
self.flags |
||||
.contains(FormalParameterListFlags::HAS_REST_PARAMETER) |
||||
} |
||||
|
||||
/// Indicates if the parameter list has expressions in it's parameters.
|
||||
pub(crate) fn has_expressions(&self) -> bool { |
||||
self.flags |
||||
.contains(FormalParameterListFlags::HAS_EXPRESSIONS) |
||||
} |
||||
|
||||
/// Indicates if the parameter list has parameters named 'arguments'.
|
||||
pub(crate) fn has_arguments(&self) -> bool { |
||||
self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS) |
||||
} |
||||
} |
||||
|
||||
bitflags! { |
||||
/// Flags for a [`FormalParameterList`].
|
||||
#[allow(clippy::unsafe_derive_deserialize)] |
||||
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] |
||||
pub(crate) struct FormalParameterListFlags: u8 { |
||||
const IS_SIMPLE = 0b0000_0001; |
||||
const HAS_DUPLICATES = 0b0000_0010; |
||||
const HAS_REST_PARAMETER = 0b0000_0100; |
||||
const HAS_EXPRESSIONS = 0b0000_1000; |
||||
const HAS_ARGUMENTS = 0b0001_0000; |
||||
} |
||||
} |
||||
|
||||
impl Default for FormalParameterListFlags { |
||||
fn default() -> Self { |
||||
Self::empty().union(Self::IS_SIMPLE) |
||||
} |
||||
} |
||||
|
||||
/// "Formal parameter" is a fancy way of saying "function parameter".
|
||||
///
|
||||
/// In the declaration of a function, the parameters must be identifiers,
|
||||
/// not any value like numbers, strings, or objects.
|
||||
///```text
|
||||
///function foo(formalParameter1, formalParameter2) {
|
||||
///}
|
||||
///```
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_formal_parameter
|
||||
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, PartialEq, Trace, Finalize)] |
||||
pub struct FormalParameter { |
||||
declaration: Declaration, |
||||
is_rest_param: bool, |
||||
} |
||||
|
||||
impl FormalParameter { |
||||
/// Creates a new formal parameter.
|
||||
pub(in crate::syntax) fn new<D>(declaration: D, is_rest_param: bool) -> Self |
||||
where |
||||
D: Into<Declaration>, |
||||
{ |
||||
Self { |
||||
declaration: declaration.into(), |
||||
is_rest_param, |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the formal parameter.
|
||||
pub fn names(&self) -> Vec<Sym> { |
||||
match &self.declaration { |
||||
Declaration::Identifier { ident, .. } => vec![ident.sym()], |
||||
Declaration::Pattern(pattern) => match pattern { |
||||
DeclarationPattern::Object(object_pattern) => object_pattern.idents(), |
||||
|
||||
DeclarationPattern::Array(array_pattern) => array_pattern.idents(), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// Get the declaration of the formal parameter
|
||||
pub fn declaration(&self) -> &Declaration { |
||||
&self.declaration |
||||
} |
||||
|
||||
/// Gets the initialization node of the formal parameter, if any.
|
||||
pub fn init(&self) -> Option<&Node> { |
||||
self.declaration.init() |
||||
} |
||||
|
||||
/// Gets wether the parameter is a rest parameter.
|
||||
pub fn is_rest_param(&self) -> bool { |
||||
self.is_rest_param |
||||
} |
||||
|
||||
pub fn is_identifier(&self) -> bool { |
||||
matches!(&self.declaration, Declaration::Identifier { .. }) |
||||
} |
||||
} |
||||
|
||||
impl ToInternedString for FormalParameter { |
||||
fn to_interned_string(&self, interner: &Interner) -> String { |
||||
let mut buf = if self.is_rest_param { |
||||
"...".to_owned() |
||||
} else { |
||||
String::new() |
||||
}; |
||||
buf.push_str(&self.declaration.to_interned_string(interner)); |
||||
buf |
||||
} |
||||
} |
Loading…
Reference in new issue