use crate::engine::api::error::Error;
use crate::engine::api::error::ErrorKind;
use crate::engine::code::bytecode::Instruction;
use crate::engine::compiler::MAX_LOCAL_VARIABLES;
use crate::engine::compiler::lexer::Punctuator;
use crate::engine::compiler::lexer::Span;
use crate::engine::compiler::model::bindings::BindingKind;
use crate::engine::compiler::model::bindings::BindingStorage;
use crate::engine::compiler::model::ir::IrConstant;
use crate::engine::compiler::model::ir::PrivateFieldAccess;
use crate::engine::compiler::model::scope::ScopeKind;
use crate::engine::compiler::names::NameId;
use crate::engine::compiler::parser::context::Parser;
use crate::engine::compiler::parser::diagnostics::source_offset;
use crate::engine::compiler::private_reference;
use crate::engine::value::JsString;
use crate::engine::value::PrimitiveValue as Value;
use super::{ClassElementState, ClassMethodFlavor};
use crate::engine::code::bytecode::DefineMethodKind;
impl<'source> Parser<'source> {
fn register_private_binding(
&mut self,
name: NameId,
span: Span,
kind: BindingKind,
) -> Result<u16, Error> {
let scope = self.current_ir().context.current_scope;
if self
.current_ir()
.scopes
.get(scope.index())
.is_none_or(|scope| scope.kind != ScopeKind::ClassPrivate)
{
return Err(Error::internal(
"private field declaration escaped the class-private scope",
));
}
if self.current_ir().binding_id_in_scope(scope, name).is_some() {
return Err(self.syntax_here("private class field is already defined"));
}
let function = self.current_ir_mut();
if function.locals.len() >= MAX_LOCAL_VARIABLES {
return Err(
Error::new(ErrorKind::JsInternal, "too many local variables").with_span(span),
);
}
let local = u16::try_from(function.locals.len())
.map_err(|_| Error::new(ErrorKind::JsInternal, "too many local variables"))?;
function.locals.push(name);
function.add_binding(
scope,
scope,
name,
BindingStorage::Local(local),
kind,
Some(span),
);
Ok(local)
}
fn register_private_accessor_primary(
&mut self,
name: NameId,
span: Span,
is_static: bool,
method_kind: DefineMethodKind,
) -> Result<u16, Error> {
let incoming = match method_kind {
DefineMethodKind::Getter => BindingKind::PrivateGetter { is_static },
DefineMethodKind::Setter => BindingKind::PrivateSetter { is_static },
DefineMethodKind::Method => {
return Err(Error::internal(
"ordinary private method reached accessor registration",
));
}
};
let scope = self.current_ir().context.current_scope;
let existing = self.current_ir().binding_id_in_scope(scope, name);
let primary_local = if let Some(binding_id) = existing {
let (existing_kind, storage) = {
let binding = self
.current_ir()
.bindings
.get(binding_id.0)
.ok_or_else(|| Error::internal("private accessor binding disappeared"))?;
(binding.kind, binding.storage)
};
let pairable = matches!(
(existing_kind, incoming),
(
BindingKind::PrivateGetter {
is_static: existing_static
},
BindingKind::PrivateSetter {
is_static: incoming_static
}
) | (
BindingKind::PrivateSetter {
is_static: existing_static
},
BindingKind::PrivateGetter {
is_static: incoming_static
}
) if existing_static == incoming_static
);
if !pairable {
return Err(self.syntax_here("private class field is already defined"));
}
self.current_ir_mut().bindings[binding_id.0].kind =
BindingKind::PrivateGetterSetter { is_static };
match storage {
BindingStorage::Local(local) => local,
BindingStorage::Argument(_)
| BindingStorage::External(_)
| BindingStorage::Module(_)
| BindingStorage::Global => {
return Err(Error::internal(
"private accessor primary binding has invalid storage",
));
}
}
} else {
self.register_private_binding(name, span, incoming)?
};
Ok(primary_local)
}
fn register_private_setter_storage(
&mut self,
name: NameId,
span: Span,
is_static: bool,
) -> Result<u16, Error> {
let setter_name = private_reference::private_setter_binding_name(&mut self.names, name);
self.register_private_binding(setter_name, span, BindingKind::PrivateSetter { is_static })
}
pub(super) fn parse_private_class_field(
&mut self,
elements: &mut ClassElementState,
is_static: bool,
name: NameId,
span: Span,
) -> Result<(), Error> {
if self.names.name(name) == "#constructor" {
return Err(self.syntax_here("invalid method name"));
}
let local =
self.register_private_binding(name, span, BindingKind::PrivateField { is_static })?;
self.emit_instruction_at(
Instruction::InitializePrivateName(local),
source_offset(span)?,
)?;
let child = self.ensure_class_initializer(elements, is_static, span)?;
let parent = self.enter_class_field_initializer(child);
self.emit_instruction(Instruction::PushThis)?;
if self.consume_punctuator(Punctuator::Equal)? {
self.parse_assignment()?;
} else {
self.emit_instruction(Instruction::Undefined)?;
self.anonymous_function_definition = None;
}
if let Some(definition) = self.take_anonymous_function_definition() {
let js_name = JsString::try_from_utf8(self.names.name(name))?;
let name_constant = self.add_constant(IrConstant::Primitive(Value::String(js_name)))?;
self.emit_anonymous_set_name(definition, Instruction::SetName(name_constant))?;
}
let scope = self.current_ir().context.current_scope;
self.emit_private_field_operation(
name,
span,
scope,
PrivateFieldAccess::Define,
source_offset(span)?,
)?;
self.emit_instruction(Instruction::Drop)?;
self.leave_class_field_initializer(parent);
self.anonymous_function_definition = None;
self.consume_statement_terminator()
}
pub(super) fn parse_private_class_method(
&mut self,
elements: &mut ClassElementState,
is_static: bool,
name: NameId,
span: Span,
function_span: Span,
flavor: ClassMethodFlavor,
) -> Result<(), Error> {
if self.names.name(name) == "#constructor" {
return Err(self.syntax_here("invalid method name"));
}
let method = match flavor {
ClassMethodFlavor::Ordinary => {
self.parse_object_method_definition(function_span, DefineMethodKind::Method)?
}
ClassMethodFlavor::Generator => {
self.parse_generator_method_definition(function_span)?
}
ClassMethodFlavor::Async => self.parse_async_method_definition(function_span)?,
ClassMethodFlavor::AsyncGenerator => {
self.parse_async_generator_method_definition(function_span)?
}
};
self.functions[method].needs_home_object = true;
let local =
self.register_private_binding(name, span, BindingKind::PrivateMethod { is_static })?;
let initializer = self.ensure_class_initializer(elements, is_static, span)?;
self.functions[initializer].class_private_brand = true;
self.emit_instruction_at(
Instruction::InitializePrivateMethod(local),
source_offset(span)?,
)?;
self.anonymous_function_definition = None;
Ok(())
}
pub(super) fn parse_private_class_accessor(
&mut self,
elements: &mut ClassElementState,
is_static: bool,
name: NameId,
span: Span,
function_span: Span,
method_kind: DefineMethodKind,
) -> Result<(), Error> {
if self.names.name(name) == "#constructor" {
return Err(self.syntax_here("invalid method name"));
}
if method_kind == DefineMethodKind::Method {
return Err(Error::internal(
"ordinary private method reached accessor lowering",
));
}
let primary = self.register_private_accessor_primary(name, span, is_static, method_kind)?;
let accessor = self.parse_object_method_definition(function_span, method_kind)?;
self.functions[accessor].needs_home_object = true;
let local = if method_kind == DefineMethodKind::Setter {
self.register_private_setter_storage(name, span, is_static)?
} else {
primary
};
let initializer = self.ensure_class_initializer(elements, is_static, span)?;
self.functions[initializer].class_private_brand = true;
self.emit_instruction_at(
Instruction::InitializePrivateAccessor(local),
source_offset(span)?,
)?;
self.anonymous_function_definition = None;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::engine::compiler::lexer::Lexer;
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::SuperCapabilities;
use crate::engine::compiler::names::NameTable;
use crate::engine::compiler::parser::builder::FunctionBuilder;
use crate::engine::compiler::parser::context::InMode;
use crate::engine::compiler::parser::context::ModuleDeclarationExport;
use crate::source::SourceOffset;
use super::*;
fn private_accessor_parser(source: &str) -> Parser<'_> {
let mut lexer = Lexer::new(source);
let first_token = lexer.next_token().unwrap();
let source_span = first_token.span;
let mut names = NameTable::new();
let root = FunctionBuilder::new(
None,
FunctionKind::Script,
FunctionSourceInfo {
span: source_span,
definition: SourceOffset::try_from_usize(0).unwrap(),
range: None,
},
FunctionIrOptions {
function_name: Some(names.intern("<private-accessor-order-test>")),
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::NONE,
},
&mut names,
)
.unwrap();
let mut parser = Parser {
token_context: (
lexer.context(),
crate::engine::compiler::lexer::LexicalGoal::Div,
),
lexer,
token: first_token,
previous_end: None,
current_function: 0,
in_mode: InMode::Allow,
functions: vec![root],
names,
module: None,
module_declaration_export: ModuleDeclarationExport::None,
module_declaration_export_target: None,
anonymous_function_definition: None,
pending_unsupported: None,
lookahead: Default::default(),
stack_guard: crate::engine::compiler::stack_guard::ParserStackGuard::new(),
};
parser.push_scope(ScopeKind::ClassPrivate);
parser
}
#[test]
fn malformed_setter_does_not_reserve_synthetic_storage_before_body_parse() {
let mut parser = private_accessor_parser("(...rest) {}");
let padding = parser.names.intern("<padding>");
parser.functions[0]
.locals
.resize(MAX_LOCAL_VARIABLES - 1, padding);
let span = parser.current().span;
let mut elements = ClassElementState::default();
let value = parser.names.intern("#value");
let error = parser
.parse_private_class_accessor(
&mut elements,
false,
value,
span,
span,
DefineMethodKind::Setter,
)
.unwrap_err();
assert_eq!(
error.message(),
"invalid number of arguments for getter or setter"
);
let root = &parser.functions[0];
assert_eq!(root.locals.len(), MAX_LOCAL_VARIABLES);
assert!(
root.binding_id_in_scope(root.context.current_scope, value)
.is_some()
);
assert!(
root.binding_id_in_scope(
root.context.current_scope,
parser.names.intern("#value<set>")
)
.is_none()
);
}
}