use super::function::ParsedFunctionDefinition;
use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::code::bytecode::Instruction;
use crate::engine::compiler::lexer::Keyword;
use crate::engine::compiler::lexer::LexicalGoal;
use crate::engine::compiler::lexer::NumberKind;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::lexer::Token;
use crate::engine::compiler::lexer::TokenKind;
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::model::scope::ScopeKind;
use crate::engine::compiler::names::NameId;
use crate::engine::compiler::parser::builder::FunctionBuilder;
use crate::engine::compiler::parser::context::AnonymousFunctionDefinition;
use crate::engine::compiler::parser::context::ModuleDeclarationExport;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::IdentifierContext;
use crate::engine::compiler::parser::diagnostics::lex_error;
use crate::engine::compiler::parser::diagnostics::source_offset;
use crate::engine::compiler::parser::literals::parse_number;
use crate::engine::value::JsString;
use crate::engine::value::PrimitiveValue as Value;
use crate::source::SourceOffset;
use crate::engine::code::bytecode::DefineMethodKind;
use crate::engine::compiler::lexer::quickjs_simple_lookahead_has_line_terminator;
mod fields;
mod private;
mod static_block;
use fields::ClassElementState;
#[derive(Clone, Debug)]
enum ClassPropertyKey {
Fixed { value: JsString },
Computed,
Private { name: NameId, span: Span },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ClassMethodFlavor {
Ordinary,
Generator,
Async,
AsyncGenerator,
}
impl<'source> Parser<'source> {
pub(super) fn parse_class_declaration(&mut self) -> Result<(), Error> {
self.parse_class(false)
}
pub(super) fn parse_class_expression(&mut self) -> Result<(), Error> {
self.parse_class(true)
}
fn parse_class(&mut self, expression: bool) -> Result<(), Error> {
let class_token = *self.current();
let class_start = source_offset(class_token.span)?;
let outer_strict = self.current_ir().strict;
self.current_ir_mut().strict = true;
self.relex_current_with_strict(true)?;
self.advance()?;
let name = if let TokenKind::Identifier(identifier) = self.current().kind {
let span = self.current().span;
self.validate_identifier_reservation(
&identifier,
span,
true,
IdentifierContext::Variable,
)?;
self.advance()?;
Some((self.intern_identifier(&identifier), span))
} else {
None
};
let default_declaration = !expression
&& self.current_module_declaration_export() == ModuleDeclarationExport::Default;
if !expression && name.is_none() && !default_declaration {
return Err(self.syntax_here("class statement requires a name"));
}
let outer_binding = if expression {
None
} else if let Some((name, span)) = &name {
Some((*name, *span))
} else {
Some((
self.intern_name(crate::engine::code::module::MODULE_DEFAULT_BINDING_NAME),
class_token.span,
))
};
let mut deferred_redeclaration = None;
if let Some((outer_name, outer_span)) = outer_binding
&& let Err(error) = self.register_lexical_binding(
outer_name,
outer_span,
self.current().span,
false,
false,
)
{
if error.kind() != ErrorKind::Syntax {
return Err(error);
}
deferred_redeclaration = Some(error);
}
let class_scope = self.push_scope(ScopeKind::Block);
if let Some((name, span)) = &name {
self.register_lexical_binding(*name, *span, self.current().span, true, false)?;
}
let has_heritage = if matches!(self.current().kind, TokenKind::Keyword(Keyword::Extends)) {
self.advance_expression_start()?;
self.parse_left_hand_side_expression()?;
self.anonymous_function_definition = None;
true
} else {
self.emit_instruction(Instruction::Undefined)?;
false
};
self.expect_punctuator(Punctuator::LeftBrace)?;
let private_scope = self.push_scope(ScopeKind::ClassPrivate);
let constructor_placeholder = self.emit(IrOp::MakeClosure(u32::MAX))?;
let class_name = match &name {
Some((name, _)) => JsString::try_from_utf8(self.names.name(*name))?,
None if default_declaration => JsString::from_static("default"),
None => JsString::from_static(""),
};
let class_name_constant =
self.add_constant(IrConstant::Primitive(Value::String(class_name)))?;
self.emit_instruction(Instruction::DefineClass {
name: class_name_constant,
has_heritage,
})?;
let mut constructor = None;
let mut elements = ClassElementState::default();
while !self.is_punctuator(Punctuator::RightBrace) {
if self.at_eof() {
return Err(self.syntax_here("unterminated class body"));
}
if self.consume_punctuator(Punctuator::Semicolon)? {
continue;
}
self.parse_class_element(&mut constructor, &mut elements, has_heritage)?;
}
let closing_brace = self.current().span;
self.advance()?;
let (constructor_constant, constructor_child) = match constructor {
Some(parsed) => (parsed.constant, parsed.child),
None if has_heritage => self.synthesize_derived_class_constructor(class_token.span)?,
None => self.synthesize_base_class_constructor(class_token.span)?,
};
let class_end = SourceOffset::try_from_usize(closing_brace.end.byte_offset)
.map_err(|error| Error::internal(error.to_string()))?;
let constructor_name = name
.as_ref()
.map_or_else(|| self.names.intern(""), |(name, _)| *name);
{
let constructor = self
.functions
.get_mut(constructor_child)
.ok_or_else(|| Error::internal("class constructor child disappeared"))?;
constructor.class_constructor = true;
constructor.derived_class_constructor = has_heritage;
constructor.function_name = Some(constructor_name);
constructor.source.span = class_token.span;
constructor.source.definition = class_start;
constructor.source.range = Some(class_start..class_end);
}
let placeholder = self
.current_ir_mut()
.ops
.get_mut(constructor_placeholder)
.ok_or_else(|| Error::internal("class constructor placeholder disappeared"))?;
let IrOp::MakeClosure(index) = &mut placeholder.op else {
return Err(Error::internal(
"class constructor placeholder changed instruction kind",
));
};
*index = constructor_constant;
self.finish_class_instance_initializer(&mut elements)?;
self.emit_instruction(Instruction::Drop)?;
if let Some((name, span)) = &name {
self.emit_instruction(Instruction::Dup)?;
self.emit_identifier(*name, *span, IdentifierAccess::Initialize)?;
}
let static_initializer_start = self.finish_class_static_initializer(&mut elements)?;
self.pop_scope(private_scope)?;
self.pop_scope(class_scope)?;
self.current_ir_mut().strict = outer_strict;
self.relex_current_with_strict(outer_strict)?;
if let Some(error) = deferred_redeclaration {
return Err(error.with_span(self.current().span));
}
if expression {
self.anonymous_function_definition =
name.is_none()
.then_some(AnonymousFunctionDefinition::Class {
owner: self.current_function,
static_initializer_start,
});
} else {
let (outer_name, outer_span) = outer_binding
.ok_or_else(|| Error::internal("class declaration lost its outer binding"))?;
self.emit_identifier(outer_name, outer_span, IdentifierAccess::Initialize)?;
self.anonymous_function_definition = None;
}
Ok(())
}
fn parse_class_element(
&mut self,
constructor: &mut Option<ParsedFunctionDefinition>,
elements: &mut ClassElementState,
has_heritage: bool,
) -> Result<(), Error> {
let element_span = self.current().span;
let mut is_static = false;
if matches!(self.current().kind, TokenKind::Keyword(Keyword::Static)) {
let next = self.class_token_after_current()?;
if !matches!(
next.kind,
TokenKind::Punctuator(
Punctuator::LeftParen
| Punctuator::Semicolon
| Punctuator::Equal
| Punctuator::RightBrace
)
) {
is_static = true;
self.advance()?;
if self.is_punctuator(Punctuator::LeftBrace) {
self.parse_class_static_block(elements, element_span)?;
return Ok(());
}
}
}
let function_span = self.current().span;
let generator = self.is_punctuator(Punctuator::Multiply);
if generator {
self.advance()?;
}
let asynchronous = !generator && self.contextual_class_async_method_ahead()?;
let async_generator = if asynchronous {
self.advance()?;
if self.is_punctuator(Punctuator::Multiply) {
self.advance()?;
true
} else {
false
}
} else {
false
};
let mut method_kind = DefineMethodKind::Method;
if !generator
&& !asynchronous
&& let TokenKind::Identifier(identifier) = &self.current().kind
&& !identifier.has_escape
&& matches!(identifier.raw, "get" | "set")
{
let next = self.class_token_after_current()?;
if !next.line_terminator_before && Self::class_property_name_starts(&next.kind) {
method_kind = if identifier.raw == "get" {
DefineMethodKind::Getter
} else {
DefineMethodKind::Setter
};
self.advance()?;
}
}
if is_static {
self.emit_instruction(Instruction::Swap)?;
}
let key = self.parse_class_property_key()?;
if !self.is_punctuator(Punctuator::LeftParen) {
if generator || asynchronous || method_kind != DefineMethodKind::Method {
return Err(self.syntax_here("invalid property name"));
}
if is_static
&& matches!(&key, ClassPropertyKey::Fixed { value }
if *value == JsString::from_static("prototype"))
{
return Err(self.syntax_here("invalid method name"));
}
match key {
ClassPropertyKey::Private { name, span } => {
self.parse_private_class_field(elements, is_static, name, span)?;
}
key => self.parse_public_class_field(elements, is_static, key, function_span)?,
}
if is_static {
self.emit_instruction(Instruction::Swap)?;
}
return Ok(());
}
if let ClassPropertyKey::Private { name, span } = &key {
if method_kind == DefineMethodKind::Method {
let flavor = if async_generator {
ClassMethodFlavor::AsyncGenerator
} else if generator {
ClassMethodFlavor::Generator
} else if asynchronous {
ClassMethodFlavor::Async
} else {
ClassMethodFlavor::Ordinary
};
self.parse_private_class_method(
elements,
is_static,
*name,
*span,
function_span,
flavor,
)?;
} else {
self.parse_private_class_accessor(
elements,
is_static,
*name,
*span,
function_span,
method_kind,
)?;
}
if is_static {
self.emit_instruction(Instruction::Swap)?;
}
return Ok(());
}
let fixed = match &key {
ClassPropertyKey::Fixed { value } => Some(value),
ClassPropertyKey::Computed => None,
ClassPropertyKey::Private { .. } => unreachable!("private methods were lowered"),
};
let is_constructor_name =
fixed.is_some_and(|name| *name == JsString::from_static("constructor"));
if !is_static
&& is_constructor_name
&& (method_kind != DefineMethodKind::Method || generator || asynchronous)
{
return Err(self.syntax_here("invalid method name"));
}
if is_static && fixed.is_some_and(|name| *name == JsString::from_static("prototype")) {
return Err(self.syntax_here("invalid method name"));
}
if !is_static
&& !generator
&& !asynchronous
&& method_kind == DefineMethodKind::Method
&& is_constructor_name
{
if constructor.is_some() {
return Err(Error::syntax(
"property constructor appears more than once",
function_span,
));
}
*constructor =
Some(self.parse_class_constructor_definition(function_span, has_heritage)?);
self.anonymous_function_definition = None;
} else {
if async_generator {
self.parse_async_generator_method_definition(function_span)?;
} else if generator {
self.parse_generator_method_definition(function_span)?;
} else if asynchronous {
self.parse_async_method_definition(function_span)?;
} else {
self.parse_object_method_definition(function_span, method_kind)?;
}
match key {
ClassPropertyKey::Fixed { value } => {
let key = self.add_constant(IrConstant::Primitive(Value::String(value)))?;
self.emit_instruction(Instruction::DefineMethod {
key,
kind: method_kind,
enumerable: false,
})?;
}
ClassPropertyKey::Computed => {
self.emit_instruction(Instruction::DefineMethodComputed {
kind: method_kind,
enumerable: false,
})?;
}
ClassPropertyKey::Private { .. } => unreachable!("private methods were lowered"),
}
}
if is_static {
self.emit_instruction(Instruction::Swap)?;
}
Ok(())
}
fn parse_class_property_key(&mut self) -> Result<ClassPropertyKey, Error> {
let token = *self.current();
let value = match token.kind {
TokenKind::Identifier(identifier) => {
self.advance()?;
self.intern_identifier_string(&identifier)?
}
TokenKind::Keyword(keyword) => {
self.advance()?;
JsString::from_static(keyword.as_str())
}
TokenKind::String(string) => {
if string.has_legacy_octal_escape {
return Err(Error::syntax(
"legacy octal escapes are forbidden in strict mode",
token.span,
));
}
self.advance()?;
self.decode_string_literal(token.span)?
}
TokenKind::Number(number) => {
if matches!(
number.kind,
NumberKind::LegacyOctal | NumberKind::LegacyDecimal
) {
return Err(Error::syntax(
"legacy leading-zero numeric literals are forbidden in strict mode",
token.span,
));
}
self.advance()?;
parse_number(&number)
.map_err(|message| Error::syntax(message, token.span))?
.to_js_string()?
}
TokenKind::Punctuator(Punctuator::LeftBracket) => {
self.advance_expression_start()?;
self.parse_assignment_allow_in()?;
self.emit_instruction(Instruction::ToPropKey)?;
self.expect_punctuator(Punctuator::RightBracket)?;
return Ok(ClassPropertyKey::Computed);
}
TokenKind::PrivateIdentifier(identifier) => {
let is_constructor = self.identical_name(&identifier, "constructor");
self.advance()?;
if is_constructor {
return Err(self.syntax_here("invalid method name"));
}
let name = self.intern_private_identifier(&identifier);
return Ok(ClassPropertyKey::Private {
name,
span: token.span,
});
}
_ => return Err(self.syntax_here("invalid property name")),
};
Ok(ClassPropertyKey::Fixed { value })
}
fn contextual_class_async_method_ahead(&self) -> Result<bool, Error> {
let TokenKind::Identifier(identifier) = &self.current().kind else {
return Ok(false);
};
if !self.is_unescaped_name(identifier, "async") {
return Ok(false);
}
let next = self.class_token_after_current()?;
let has_line_terminator = quickjs_simple_lookahead_has_line_terminator(
&self.lexer.source()[self.current().span.end.byte_offset..],
);
Ok(!has_line_terminator
&& !matches!(
next.kind,
TokenKind::Punctuator(
Punctuator::Colon
| Punctuator::Comma
| Punctuator::RightBrace
| Punctuator::LeftParen
| Punctuator::Equal
)
))
}
fn class_property_name_starts(kind: &TokenKind<'_>) -> bool {
matches!(
kind,
TokenKind::Identifier(_)
| TokenKind::Keyword(_)
| TokenKind::String(_)
| TokenKind::Number(_)
| TokenKind::PrivateIdentifier(_)
| TokenKind::Punctuator(Punctuator::LeftBracket)
)
}
fn class_token_after_current(&self) -> Result<Token<'source>, Error> {
let mut lexer = self.lexer.clone();
lexer.seek(self.current().span.end);
self.probe_token(&mut lexer, LexicalGoal::Div)
.map_err(lex_error)
}
fn synthesize_base_class_constructor(
&mut self,
class_span: Span,
) -> Result<(u32, FunctionId), Error> {
let parent = self.current_function;
let child = self.functions.len();
let definition_scope = self.current_ir().context.current_scope;
self.functions.push(FunctionBuilder::new(
Some(ParentLink {
function: parent,
definition_scope,
}),
FunctionKind::Method,
FunctionSourceInfo {
span: class_span,
definition: source_offset(class_span)?,
range: None,
},
FunctionIrOptions {
function_name: None,
private_name_binding: false,
class_constructor: true,
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.current_function = child;
self.emit_instruction(Instruction::CheckCtor)?;
self.emit_instruction(Instruction::PushThis)?;
self.emit_instruction(Instruction::PushActiveFunction)?;
self.emit_instruction(Instruction::CallClassInstanceInitializer)?;
self.emit_instruction(Instruction::Drop)?;
self.emit_instruction(Instruction::Undefined)?;
self.emit_instruction(Instruction::Return)?;
self.current_function = parent;
let constant = self.add_constant(IrConstant::Child(child))?;
Ok((constant, child))
}
fn synthesize_derived_class_constructor(
&mut self,
class_span: Span,
) -> Result<(u32, FunctionId), Error> {
let parent = self.current_function;
let child = self.functions.len();
let definition_scope = self.current_ir().context.current_scope;
self.functions.push(FunctionBuilder::new(
Some(ParentLink {
function: parent,
definition_scope,
}),
FunctionKind::Method,
FunctionSourceInfo {
span: class_span,
definition: source_offset(class_span)?,
range: None,
},
FunctionIrOptions {
function_name: None,
private_name_binding: false,
class_constructor: true,
derived_class_constructor: true,
parameters: Vec::new(),
defined_argument_count: 0,
has_simple_parameter_list: true,
rest_parameter: None,
strict: true,
super_capabilities: SuperCapabilities::CALL_AND_PROPERTY,
},
&mut self.names,
)?);
self.functions[child].allocate_derived_constructor_pseudo_bindings(&mut self.names)?;
self.current_function = child;
let this = self
.current_ir()
.this_local
.ok_or_else(|| Error::internal("derived constructor has no this binding"))?;
self.emit_instruction(Instruction::CheckCtor)?;
self.emit_instruction(Instruction::InitDerivedConstructor)?;
self.emit_instruction(Instruction::Dup)?;
self.emit_instruction(Instruction::InitializeDerivedLocal(this))?;
let active = self
.current_ir()
.active_function_local
.ok_or_else(|| Error::internal("derived constructor has no active function"))?;
self.emit_instruction(Instruction::GetLocal(active))?;
self.emit_instruction(Instruction::CallClassInstanceInitializer)?;
self.emit_instruction(Instruction::ReturnDerived(this))?;
self.current_function = parent;
let constant = self.add_constant(IrConstant::Child(child))?;
Ok((constant, child))
}
}