use std::collections::HashMap;
use std::rc::Rc;
use hermes_ast::context::{GCLock, NodeRc};
use hermes_ast::node::{Identifier, Node};
use hermes_ast::{NodeId, SemaId};
use hermes_support::persistent_scoped_map::{PersistentScopedMap, Scope, ScopePtr};
use crate::ids::{DeclId, FunctionInfoId, ScopeId};
use crate::keywords::Keywords;
pub type Atom = hermes_atom_table::AtomBytes;
pub fn private_name_identifier(gc: &GCLock, name: Atom) -> Atom {
let name_bytes = gc.bytes(name);
let mut mangled = Vec::with_capacity(1 + name_bytes.len());
mangled.push(b'#');
mangled.extend_from_slice(name_bytes);
gc.atom_bytes(mangled)
}
#[derive(Debug, Clone)]
pub struct Binding {
pub decl: DeclId,
pub ident: Option<NodeRc>,
}
impl Binding {
pub fn new(decl: DeclId, ident: Option<NodeRc>) -> Binding {
Binding { decl, ident }
}
}
pub type BindingTable = PersistentScopedMap<Atom, Binding>;
pub type BindingTableScope<'m> = Scope<'m, Atom, Binding>;
pub type BindingTableScopePtr = ScopePtr<Atom, Binding>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum DeclKind {
Let,
Const,
Class,
Import,
Catch,
ScopedFunction,
ES5Catch,
FunctionExprName,
ClassExprName,
TypedBuiltin,
PrivateField,
PrivateMethod,
PrivateGetter,
PrivateSetter,
PrivateGetterSetter,
Var,
Parameter,
GlobalProperty,
UndeclaredGlobalProperty,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeclSpecial {
NotSpecial,
Arguments,
Eval,
PrivateStatic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Constness {
Never,
StrictModeOnly,
Always,
}
impl DeclKind {
pub fn is_tdz(self) -> bool {
self <= DeclKind::Class
}
pub fn is_var_like(self) -> bool {
self >= DeclKind::Var
}
pub fn is_var_like_or_scoped_function(self) -> bool {
self.is_var_like() || self == DeclKind::ScopedFunction
}
pub fn is_let_like(self) -> bool {
self <= DeclKind::ES5Catch
}
pub fn is_global(self) -> bool {
self >= DeclKind::GlobalProperty
}
pub fn constness(self) -> Constness {
match self {
DeclKind::Const
| DeclKind::ClassExprName
| DeclKind::Import => Constness::Always,
DeclKind::FunctionExprName => Constness::StrictModeOnly,
_ => Constness::Never,
}
}
pub fn is_private_name(self) -> bool {
self >= DeclKind::PrivateField && self <= DeclKind::PrivateGetterSetter
}
}
#[derive(Debug, Clone)]
pub struct Decl {
pub name: Atom,
pub kind: DeclKind,
pub generic: bool,
pub special: DeclSpecial,
pub scope: Option<ScopeId>,
}
pub struct LexicalScope {
pub depth: u32,
pub parent_function: FunctionInfoId,
pub parent_scope: Option<ScopeId>,
pub idx_in_parent_function: u32,
pub decls: Vec<DeclId>,
pub hoisted_functions: Vec<NodeRc>,
pub local_eval: bool,
pub binding_table_scope: BindingTableScopePtr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FuncIsArrow {
Yes,
No,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstructorKind {
None,
Base,
Derived,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum SourceVisibility {
#[default]
Default,
ShowSource,
HideSource,
Sensitive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CustomDirectives {
pub source_visibility: SourceVisibility,
pub always_inline: bool,
pub no_inline: bool,
pub builtin: bool,
}
pub struct FunctionInfo {
scopes: Vec<ScopeId>,
pub parent_function: Option<FunctionInfoId>,
pub parent_scope: Option<ScopeId>,
pub imports: Vec<NodeRc>,
pub arguments_decl: Option<DeclId>,
pub function_body_scope_idx: u32,
pub strict: bool,
pub custom_directives: CustomDirectives,
pub arrow: bool,
pub constructor_kind: ConstructorKind,
pub simple_parameter_list: bool,
pub has_parameter_expressions: bool,
pub uses_arguments: bool,
pub contains_arrow_functions: bool,
pub contains_arrow_functions_using_arguments: bool,
pub may_reach_implicit_return: bool,
pub is_program_node: bool,
pub is_static_block: bool,
pub binding_table_scope: BindingTableScopePtr,
pub num_labels: u32,
}
impl FunctionInfo {
pub const NO_FUNCTION_BODY_SCOPE: u32 = u32::MAX;
pub fn new(
is_arrow: FuncIsArrow,
cons_kind: ConstructorKind,
parent_function: Option<FunctionInfoId>,
parent_scope: Option<ScopeId>,
strict: bool,
custom_directives: CustomDirectives,
) -> FunctionInfo {
FunctionInfo {
scopes: Vec::new(),
parent_function,
parent_scope,
imports: Vec::new(),
arguments_decl: None,
function_body_scope_idx: Self::NO_FUNCTION_BODY_SCOPE,
strict,
custom_directives,
arrow: is_arrow == FuncIsArrow::Yes,
constructor_kind: cons_kind,
simple_parameter_list: true,
has_parameter_expressions: false,
uses_arguments: false,
contains_arrow_functions: false,
contains_arrow_functions_using_arguments: false,
may_reach_implicit_return: true,
is_program_node: false,
is_static_block: false,
binding_table_scope: BindingTableScopePtr::default(),
num_labels: 0,
}
}
pub fn get_function_body_scope(&self) -> ScopeId {
debug_assert!(
(self.function_body_scope_idx as usize) < self.scopes.len(),
"functionScopeIdx not set"
);
self.scopes[self.function_body_scope_idx as usize]
}
pub fn get_parameter_scope(&self) -> ScopeId {
debug_assert!(!self.scopes.is_empty(), "no parameter scope added yet");
self.scopes[0]
}
pub fn get_scopes(&self) -> &[ScopeId] {
&self.scopes
}
pub fn add_scope(&mut self, scope: ScopeId) -> u32 {
let idx = self.scopes.len() as u32;
self.scopes.push(scope);
idx
}
pub fn allocate_label(&mut self) -> u32 {
let label = self.num_labels;
self.num_labels += 1;
label
}
}
mod decl_state_bits {
pub const HAVE_EXPR: u8 = 1;
pub const HAVE_DECL: u8 = 2;
pub const SIDE_DECL: u8 = 4;
}
use decl_state_bits::{HAVE_DECL, HAVE_EXPR, SIDE_DECL};
const HAVE_EXPR_AND_DECL: u8 = HAVE_EXPR | HAVE_DECL;
const HAVE_EXPR_AND_SIDE: u8 = HAVE_EXPR | SIDE_DECL;
pub struct SemContext {
pub kw: Keywords,
functions: Vec<FunctionInfo>,
scopes: Vec<LexicalScope>,
decls: Vec<Decl>,
binding_table: Rc<BindingTable>,
binding_table_global_scope: BindingTableScopePtr,
side_identifier_declaration_decl: HashMap<NodeId, DeclId>,
promoted_function_decls: HashMap<NodeId, DeclId>,
builtin_declarations: Vec<NodeRc>,
}
impl SemContext {
pub fn new(kw: Keywords) -> SemContext {
SemContext {
kw,
functions: Vec::new(),
scopes: Vec::new(),
decls: Vec::new(),
binding_table: Rc::new(BindingTable::new()),
binding_table_global_scope: BindingTableScopePtr::default(),
side_identifier_declaration_decl: HashMap::new(),
promoted_function_decls: HashMap::new(),
builtin_declarations: Vec::new(),
}
}
pub fn assert_global_function_and_scope(&self) {
debug_assert!(!self.functions.is_empty(), "global function has not been created");
debug_assert!(!self.scopes.is_empty(), "global scope has not been created");
}
pub fn node_is_arrow<'gc>(node: Option<&Node<'gc>>) -> FuncIsArrow {
if let Some(n) = node {
if matches!(n, Node::ArrowFunctionExpression(_)) {
return FuncIsArrow::Yes;
}
}
FuncIsArrow::No
}
pub fn new_function(
&mut self,
is_arrow: FuncIsArrow,
cons_kind: ConstructorKind,
parent_function: Option<FunctionInfoId>,
parent_scope: Option<ScopeId>,
strict: bool,
custom_directives: CustomDirectives,
) -> FunctionInfoId {
self.functions.push(FunctionInfo::new(
is_arrow,
cons_kind,
parent_function,
parent_scope,
strict,
custom_directives,
));
FunctionInfoId::from_sema_id(SemaId((self.functions.len() - 1) as u32))
}
pub fn new_scope(
&mut self,
parent_function: FunctionInfoId,
parent_scope: Option<ScopeId>,
) -> ScopeId {
let depth = match parent_scope {
Some(ps) => self.scope(ps).depth + 1,
None => 0,
};
self.scopes.push(LexicalScope {
depth,
parent_function,
parent_scope,
idx_in_parent_function: 0,
decls: Vec::new(),
hoisted_functions: Vec::new(),
local_eval: false,
binding_table_scope: BindingTableScopePtr::default(),
});
let id = ScopeId::from_sema_id(SemaId((self.scopes.len() - 1) as u32));
let idx = self.function_mut(parent_function).add_scope(id);
self.scope_mut(id).idx_in_parent_function = idx;
id
}
pub fn new_decl_in_scope(
&mut self,
name: Atom,
kind: DeclKind,
scope: ScopeId,
special: DeclSpecial,
) -> DeclId {
self.decls.push(Decl {
name,
kind,
generic: false,
special,
scope: Some(scope),
});
let id = DeclId::from_sema_id(SemaId((self.decls.len() - 1) as u32));
self.scope_mut(scope).decls.push(id);
id
}
pub fn new_decl_in_scope_default(
&mut self,
name: Atom,
kind: DeclKind,
scope: ScopeId,
) -> DeclId {
self.new_decl_in_scope(name, kind, scope, DeclSpecial::NotSpecial)
}
pub fn new_global(&mut self, name: Atom, kind: DeclKind) -> DeclId {
debug_assert!(kind.is_global(), "invalid global declaration kind");
let global_scope = self.get_global_scope();
self.new_decl_in_scope(name, kind, global_scope, DeclSpecial::NotSpecial)
}
pub fn get_global_function(&self) -> FunctionInfoId {
FunctionInfoId::from_sema_id(SemaId(0))
}
pub fn get_global_scope(&self) -> ScopeId {
ScopeId::from_sema_id(SemaId(0))
}
pub fn nearest_non_arrow(&self, f: FunctionInfoId) -> FunctionInfoId {
let mut cur = f;
let global = self.get_global_function();
while {
let info = self.function(cur);
info.arrow || (info.is_program_node && cur != global)
} {
cur = self
.function(cur)
.parent_function
.expect("All FunctionInfo should have a non-arrow ancestor.");
}
cur
}
pub fn func_arguments_decl(
&mut self,
func: FunctionInfoId,
arguments_name: Atom,
) -> DeclId {
let mut arguments_func = func;
while self.function(arguments_func).arrow {
match self.function(arguments_func).parent_function {
Some(parent) => arguments_func = parent,
None => break,
}
}
if let Some(decl) = self.function(arguments_func).arguments_decl {
return decl;
}
let decl = if arguments_func == self.get_global_function() {
let scope = self.function(arguments_func).get_scopes()[0];
self.new_decl_in_scope(
arguments_name,
DeclKind::UndeclaredGlobalProperty,
scope,
DeclSpecial::NotSpecial,
)
} else {
let scope = self.function(arguments_func).get_scopes()[0];
self.new_decl_in_scope(
arguments_name,
DeclKind::Var,
scope,
DeclSpecial::Arguments,
)
};
self.function_mut(arguments_func).arguments_decl = Some(decl);
decl
}
pub fn function(&self, id: FunctionInfoId) -> &FunctionInfo {
&self.functions[id.index()]
}
pub fn function_mut(&mut self, id: FunctionInfoId) -> &mut FunctionInfo {
&mut self.functions[id.index()]
}
pub fn scope(&self, id: ScopeId) -> &LexicalScope {
&self.scopes[id.index()]
}
pub fn scope_mut(&mut self, id: ScopeId) -> &mut LexicalScope {
&mut self.scopes[id.index()]
}
pub fn decl(&self, id: DeclId) -> &Decl {
&self.decls[id.index()]
}
pub fn decl_mut(&mut self, id: DeclId) -> &mut Decl {
&mut self.decls[id.index()]
}
pub fn functions_len(&self) -> usize {
self.functions.len()
}
pub fn set_binding_table_global_scope(&mut self, scope: BindingTableScopePtr) {
self.binding_table_global_scope = scope;
}
pub fn get_binding_table_global_scope(&self) -> &BindingTableScopePtr {
&self.binding_table_global_scope
}
pub fn binding_table(&self) -> &BindingTable {
&self.binding_table
}
pub fn binding_table_rc(&self) -> Rc<BindingTable> {
Rc::clone(&self.binding_table)
}
pub fn add_builtin_declaration(&mut self, decl: NodeRc) {
self.builtin_declarations.push(decl);
}
pub fn builtin_declarations(&self) -> &[NodeRc] {
&self.builtin_declarations
}
pub fn get_declaration_decl(&self, ident: &Identifier) -> Option<DeclId> {
let state = ident.decl_state.get();
if state & HAVE_DECL != 0 {
ident.decl.get().map(DeclId::from_sema_id)
} else if state & SIDE_DECL != 0 {
let node_id = ident.metadata.id.get();
let decl = *self
.side_identifier_declaration_decl
.get(&node_id)
.expect(
"IdentifierNode with BitSideDecl must be in the side table",
);
Some(decl)
} else {
None
}
}
pub fn get_expression_decl(&self, ident: &Identifier) -> Option<DeclId> {
assert!(
!ident.unresolvable.get(),
"Attempt to read decl for unresolvable identifier"
);
if ident.decl_state.get() & HAVE_EXPR != 0 {
ident.decl.get().map(DeclId::from_sema_id)
} else {
None
}
}
pub fn set_declaration_decl(
&mut self,
node_id: NodeId,
ident: &Identifier,
decl: Option<DeclId>,
) {
debug_assert_eq!(
node_id,
ident.metadata.id.get(),
"node_id must identify ident itself"
);
if let Some(decl) = decl {
match ident.decl_state.get() {
HAVE_EXPR => {
if ident.decl.get() == Some(decl.sema_id()) {
ident.decl_state.set(HAVE_EXPR_AND_DECL);
} else {
ident.decl_state.set(HAVE_EXPR_AND_SIDE);
self.side_identifier_declaration_decl.insert(node_id, decl);
}
}
HAVE_EXPR_AND_SIDE => {
if ident.decl.get() == Some(decl.sema_id()) {
ident.decl_state.set(HAVE_EXPR_AND_DECL);
let erased =
self.side_identifier_declaration_decl.remove(&node_id);
debug_assert!(
erased.is_some(),
"IdentifierNode with BitSideDecl must be in side table"
);
} else {
self.side_identifier_declaration_decl.insert(node_id, decl);
}
}
state => {
debug_assert!(
state == 0 || state == HAVE_DECL,
"Invalid declState"
);
ident.decl.set(Some(decl.sema_id()));
ident.decl_state.set(HAVE_DECL);
}
}
} else {
match ident.decl_state.get() {
HAVE_EXPR_AND_DECL => {
ident.decl_state.set(HAVE_EXPR);
}
HAVE_DECL => {
ident.decl_state.set(0);
ident.decl.set(None);
}
HAVE_EXPR_AND_SIDE => {
ident.decl_state.set(HAVE_EXPR);
let erased =
self.side_identifier_declaration_decl.remove(&node_id);
debug_assert!(
erased.is_some(),
"IdentifierNode with BitSideDecl must be in side table"
);
}
state => {
debug_assert!(
state == 0 || state == HAVE_EXPR,
"Invalid declState"
);
}
}
}
}
pub fn set_expression_decl(
&mut self,
node_id: NodeId,
ident: &Identifier,
decl: Option<DeclId>,
) {
debug_assert_eq!(
node_id,
ident.metadata.id.get(),
"node_id must identify ident itself"
);
if let Some(decl) = decl {
assert!(
!ident.unresolvable.get(),
"Attempt to set decl for unresolvable identifier"
);
match ident.decl_state.get() {
HAVE_DECL | HAVE_EXPR_AND_DECL => {
if Some(decl.sema_id()) == ident.decl.get() {
ident.decl_state.set(HAVE_EXPR_AND_DECL);
} else {
ident.decl_state.set(HAVE_EXPR_AND_SIDE);
if let Some(old) = ident.decl.get() {
self.side_identifier_declaration_decl.insert(
node_id,
DeclId::from_sema_id(old),
);
}
ident.decl.set(Some(decl.sema_id()));
}
}
HAVE_EXPR_AND_SIDE => {
ident.decl.set(Some(decl.sema_id()));
let side = *self
.side_identifier_declaration_decl
.get(&node_id)
.expect(
"IdentifierNode with BitSideDecl must be in side table",
);
if decl == side {
ident.decl_state.set(HAVE_EXPR_AND_DECL);
self.side_identifier_declaration_decl.remove(&node_id);
}
}
state => {
debug_assert!(state == 0 || state == HAVE_EXPR);
ident.decl.set(Some(decl.sema_id()));
ident.decl_state.set(HAVE_EXPR);
}
}
} else {
match ident.decl_state.get() {
HAVE_EXPR => {
ident.decl_state.set(0);
ident.decl.set(None);
}
HAVE_EXPR_AND_DECL => {
ident.decl_state.set(HAVE_DECL);
}
HAVE_EXPR_AND_SIDE => {
let side = *self
.side_identifier_declaration_decl
.get(&node_id)
.expect(
"IdentifierNode with BitSideDecl must be in side table",
);
ident.decl.set(Some(side.sema_id()));
ident.decl_state.set(HAVE_DECL);
}
state => {
debug_assert!(
state == 0 || state == HAVE_DECL,
"Invalid declState"
);
}
}
}
}
pub fn set_both_decl(
&mut self,
node_id: NodeId,
ident: &Identifier,
decl: Option<DeclId>,
) {
self.set_expression_decl(node_id, ident, decl);
self.set_declaration_decl(node_id, ident, decl);
}
pub fn set_promoted_decl(&mut self, node_id: NodeId, decl: DeclId) {
self.promoted_function_decls.insert(node_id, decl);
}
pub fn get_promoted_decl(&self, node_id: NodeId) -> Option<DeclId> {
self.promoted_function_decls.get(&node_id).copied()
}
pub fn clear_promoted_decls(&mut self) {
self.promoted_function_decls.clear();
}
#[doc(hidden)]
pub fn side_table_len_for_test(&self) -> usize {
self.side_identifier_declaration_decl.len()
}
pub fn get_constructor<'gc>(
&self,
class_node: &'gc Node<'gc>,
) -> Option<&'gc Node<'gc>> {
let class_body = match class_node {
Node::ClassDeclaration(n) => n.body,
Node::ClassExpression(n) => n.body,
_ => {
debug_assert!(false, "ClassLikeNode has only two subtypes.");
return None;
}
};
let body = class_body
.as_class_body()
.expect("ClassDeclaration/ClassExpression body must be a ClassBody");
body.body.iter().find(|member| {
member
.as_method_definition()
.is_some_and(|method| method.kind.get() == self.kw.ident_constructor)
})
}
}