use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::code::bytecode::Instruction;
use crate::engine::compiler::lexer::LexContext;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::model::ir::FunctionId;
use crate::engine::compiler::model::ir::IdentifierAccess;
use crate::engine::compiler::model::ir::IrConstant;
use crate::engine::compiler::model::ir::IrOp;
use crate::engine::compiler::model::ir::function::FunctionIrOptions;
use crate::engine::compiler::model::ir::function::FunctionKind;
use crate::engine::compiler::model::ir::function::FunctionSourceInfo;
use crate::engine::compiler::model::ir::function::ParentLink;
use crate::engine::compiler::model::ir::function::SuperCapabilities;
use crate::engine::compiler::parser::builder::FunctionBuilder;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::source_offset;
use crate::engine::value::JsString;
use crate::engine::value::PrimitiveValue as Value;
use super::ClassPropertyKey;
use crate::engine::code::function::metadata::ClassInitializerKind;
#[derive(Clone, Debug, Default)]
pub(super) struct ClassElementState {
instance_initializer: Option<FunctionId>,
static_initializer: Option<FunctionId>,
instance_computed_count: usize,
static_computed_count: usize,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ClassFieldInitializerParent {
function: FunctionId,
lex_context: LexContext,
}
impl<'source> Parser<'source> {
fn class_field_initializer_lex_context(mut context: LexContext) -> LexContext {
context.strict = true;
context.generator = false;
context.async_function = false;
context
}
pub(super) fn enter_class_field_initializer(
&mut self,
child: FunctionId,
) -> ClassFieldInitializerParent {
let parent = ClassFieldInitializerParent {
function: self.current_function,
lex_context: self.lexer.context(),
};
self.current_function = child;
self.set_future_lex_context(Self::class_field_initializer_lex_context(
parent.lex_context,
));
parent
}
pub(super) fn leave_class_field_initializer(&mut self, parent: ClassFieldInitializerParent) {
self.current_function = parent.function;
self.set_future_lex_context(parent.lex_context);
}
pub(super) fn ensure_class_initializer(
&mut self,
elements: &mut ClassElementState,
is_static: bool,
span: Span,
) -> Result<FunctionId, Error> {
let existing = if is_static {
elements.static_initializer
} else {
elements.instance_initializer
};
if let Some(existing) = existing {
return Ok(existing);
}
let parent = self.current_function;
let child = self.functions.len();
let definition_scope = self.current_ir().context.current_scope;
let initializer_name = if is_static {
self.names.intern("<class_static_init>")
} else {
self.names.intern("<class_fields_init>")
};
self.functions.push(FunctionBuilder::new(
Some(ParentLink {
function: parent,
definition_scope,
}),
FunctionKind::Method,
FunctionSourceInfo {
span,
definition: source_offset(span)?,
range: None,
},
FunctionIrOptions {
function_name: Some(initializer_name),
private_name_binding: false,
class_constructor: false,
derived_class_constructor: false,
parameters: Vec::new(),
defined_argument_count: 0,
has_simple_parameter_list: true,
rest_parameter: None,
strict: true,
super_capabilities: SuperCapabilities::PROPERTY,
},
&mut self.names,
)?);
self.functions[child].class_initializer_kind = Some(if is_static {
ClassInitializerKind::StaticElements
} else {
ClassInitializerKind::InstanceFields
});
self.functions[child].arguments_forbidden = true;
self.functions[child].needs_home_object = true;
if is_static {
elements.static_initializer = Some(child);
} else {
elements.instance_initializer = Some(child);
}
Ok(child)
}
pub(super) fn parse_public_class_field(
&mut self,
elements: &mut ClassElementState,
is_static: bool,
key: ClassPropertyKey,
span: Span,
) -> Result<(), Error> {
if matches!(&key, ClassPropertyKey::Fixed { value }
if *value == JsString::from_static("constructor")
|| *value == JsString::from_static("prototype"))
{
return Err(self.syntax_here("invalid field name"));
}
let child = self.ensure_class_initializer(elements, is_static, span)?;
let computed_binding = match &key {
ClassPropertyKey::Fixed { .. } => None,
ClassPropertyKey::Computed => {
let count = if is_static {
&mut elements.static_computed_count
} else {
&mut elements.instance_computed_count
};
let hidden = if is_static {
format!("<static_computed_field>{count}")
} else {
format!("<computed_field>{count}")
};
*count = count
.checked_add(1)
.ok_or_else(|| Error::new(ErrorKind::JsInternal, "too many class fields"))?;
let hidden = self.intern_name(&hidden);
self.register_lexical_binding(hidden, span, self.current().span, true, false)?;
self.emit_identifier(hidden, span, IdentifierAccess::Initialize)?;
Some(hidden)
}
ClassPropertyKey::Private { .. } => {
unreachable!("private field reached public field lowering")
}
};
let parent = self.enter_class_field_initializer(child);
self.emit_instruction(Instruction::PushThis)?;
if let Some(hidden) = computed_binding {
self.emit_identifier(hidden, span, IdentifierAccess::Get)?;
}
if self.consume_punctuator(Punctuator::Equal)? {
self.parse_assignment()?;
} else {
self.emit_instruction(Instruction::Undefined)?;
self.anonymous_function_definition = None;
}
let anonymous = self.take_anonymous_function_definition();
match key {
ClassPropertyKey::Fixed { value } => {
let key = self.add_constant(IrConstant::Primitive(Value::String(value)))?;
if let Some(definition) = anonymous {
self.emit_anonymous_set_name(definition, Instruction::SetName(key))?;
}
self.emit_instruction(Instruction::DefineField(key))?;
}
ClassPropertyKey::Computed => {
if let Some(definition) = anonymous {
self.emit_anonymous_set_name(definition, Instruction::SetNameComputed)?;
}
self.emit_instruction(Instruction::DefineFieldComputed)?;
}
ClassPropertyKey::Private { .. } => {
unreachable!("private field reached public field lowering")
}
}
self.emit_instruction(Instruction::Drop)?;
self.leave_class_field_initializer(parent);
self.anonymous_function_definition = None;
self.consume_statement_terminator()
}
pub(super) fn finish_class_instance_initializer(
&mut self,
elements: &mut ClassElementState,
) -> Result<(), Error> {
let Some(child) = elements.instance_initializer.take() else {
return Ok(());
};
let parent = self.current_function;
self.current_function = child;
self.emit_instruction(Instruction::Undefined)?;
self.emit_instruction(Instruction::Return)?;
self.current_function = parent;
let constant = self.add_constant(IrConstant::Child(child))?;
self.emit(IrOp::MakeClosure(constant))?;
self.emit_instruction(Instruction::InstallClassInstanceInitializer)?;
Ok(())
}
pub(super) fn finish_class_static_initializer(
&mut self,
elements: &mut ClassElementState,
) -> Result<Option<usize>, Error> {
let Some(child) = elements.static_initializer.take() else {
return Ok(None);
};
let parent = self.current_function;
self.current_function = child;
self.emit_instruction(Instruction::Undefined)?;
self.emit_instruction(Instruction::Return)?;
self.current_function = parent;
let constant = self.add_constant(IrConstant::Child(child))?;
let start = self.current_ir().ops.len();
self.emit(IrOp::MakeClosure(constant))?;
self.emit_instruction(Instruction::RunClassStaticInitializer)?;
Ok(Some(start))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn field_initializer_context_preserves_module_but_resets_outer_function_kind() {
let parent = LexContext {
strict: false,
module: true,
generator: true,
async_function: true,
};
assert_eq!(
Parser::<'static>::class_field_initializer_lex_context(parent),
LexContext {
strict: true,
module: true,
generator: false,
async_function: false,
}
);
}
}