Skip to content

Commit

Permalink
feat(transformer/class-properties): transform static/instance accesso…
Browse files Browse the repository at this point in the history
…r methods
  • Loading branch information
Dunqing committed Dec 28, 2024
1 parent 727cba8 commit 63234f5
Show file tree
Hide file tree
Showing 27 changed files with 1,097 additions and 356 deletions.
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/common/helper_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ pub enum Helper {
ClassPrivateFieldLooseBase,
SuperPropGet,
SuperPropSet,
ReadOnlyError,
WriteOnlyError,
}

impl Helper {
Expand All @@ -187,6 +189,8 @@ impl Helper {
Self::ClassPrivateFieldLooseBase => "classPrivateFieldLooseBase",
Self::SuperPropGet => "superPropGet",
Self::SuperPropSet => "superPropSet",
Self::ReadOnlyError => "readOnlyError",
Self::WriteOnlyError => "writeOnlyError",
}
}
}
Expand Down
44 changes: 32 additions & 12 deletions crates/oxc_transformer/src/es2022/class_properties/class.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! ES2022: Class Properties
//! Transform of class itself.
use indexmap::map::Entry;
use oxc_allocator::{Address, GetAddress};
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
Expand Down Expand Up @@ -93,7 +94,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
let binding = ctx.generate_uid_in_current_hoist_scope(&ident.name);
private_props.insert(
ident.name.clone(),
PrivateProp::new(binding, prop.r#static, false, false),
PrivateProp::new(binding, prop.r#static, None, false),
);
}

Expand Down Expand Up @@ -135,10 +136,22 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
ctx.current_block_scope_id(),
SymbolFlags::FunctionScopedVariable,
);
private_props.insert(
ident.name.clone(),
PrivateProp::new(binding, method.r#static, true, false),
);

match private_props.entry(ident.name.clone()) {
Entry::Occupied(mut entry) => {
// If there's already a binding for this private property,
// it's a setter or getter, so store the binding in `binding2`.
entry.get_mut().set_binding2(binding);
}
Entry::Vacant(entry) => {
entry.insert(PrivateProp::new(
binding,
method.r#static,
Some(method.kind),
false,
));
}
}
}
}
ClassElement::AccessorProperty(prop) => {
Expand All @@ -148,7 +161,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
let dummy_binding = BoundIdentifier::new(Atom::empty(), SymbolId::new(0));
private_props.insert(
ident.name.clone(),
PrivateProp::new(dummy_binding, prop.r#static, true, true),
PrivateProp::new(dummy_binding, prop.r#static, None, true),
);
}
}
Expand Down Expand Up @@ -443,7 +456,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
self.ctx.statement_injector.insert_many_before(
&stmt_address,
private_props.iter().filter_map(|(name, prop)| {
if prop.is_method || prop.is_accessor {
if prop.is_method() || prop.is_accessor {
return None;
}

Expand All @@ -459,10 +472,10 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
self.ctx.statement_injector.insert_many_before(
&stmt_address,
private_props.values().filter_map(|prop| {
if prop.is_static || (prop.is_method && has_method) || prop.is_accessor {
if prop.is_static || (prop.is_method() && has_method) || prop.is_accessor {
return None;
}
if prop.is_method {
if prop.is_method() {
// `var _C_brand = new WeakSet();`
has_method = true;
let binding = class_details.bindings.brand();
Expand Down Expand Up @@ -583,7 +596,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
// TODO(improve-on-babel): Simplify this.
if self.private_fields_as_properties {
exprs.extend(private_props.iter().filter_map(|(name, prop)| {
if prop.is_method || prop.is_accessor {
if prop.is_method() || prop.is_accessor {
return None;
}

Expand All @@ -598,7 +611,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
let mut weakmap_symbol_id = None;
let mut has_method = false;
exprs.extend(private_props.values().filter_map(|prop| {
if prop.is_method || prop.is_accessor {
if prop.is_method() || prop.is_accessor {
if prop.is_static || has_method {
return None;
}
Expand Down Expand Up @@ -700,6 +713,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
/// * Extract computed key assignments and insert them before class.
/// * Remove all properties, private methods and static blocks from class body.
fn transform_class_elements(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) {
let mut class_methods = vec![];
class.body.body.retain_mut(|element| {
match element {
ClassElement::PropertyDefinition(prop) => {
Expand All @@ -718,7 +732,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}
ClassElement::MethodDefinition(method) => {
self.substitute_temp_var_for_method_computed_key(method, ctx);
if self.convert_private_method(method, ctx) {
if let Some(statement) = self.convert_private_method(method, ctx) {
class_methods.push(statement);
return false;
}
}
Expand All @@ -729,6 +744,11 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {

true
});

// All methods are moved to after the class, but need to be before static properties
// TODO(improve-on-babel): insertion order doesn't matter, and it more clear to insert according to
// definition order.
self.insert_after_stmts.splice(0..0, class_methods);
}

/// Flag that static private fields should be transpiled using temp binding,
Expand Down
166 changes: 149 additions & 17 deletions crates/oxc_transformer/src/es2022/class_properties/class_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,29 @@ impl<'a> ClassDetails<'a> {
pub(super) struct PrivateProp<'a> {
pub binding: BoundIdentifier<'a>,
pub is_static: bool,
pub is_method: bool,
pub method_kind: Option<MethodDefinitionKind>,
pub is_accessor: bool,
// For accessor methods, they have two bindings,
// one for getter and one for setter.
pub binding2: Option<BoundIdentifier<'a>>,
}

impl<'a> PrivateProp<'a> {
pub fn new(
binding: BoundIdentifier<'a>,
is_static: bool,
is_method: bool,
method_kind: Option<MethodDefinitionKind>,
is_accessor: bool,
) -> Self {
Self { binding, is_static, is_method, is_accessor }
Self { binding, is_static, method_kind, is_accessor, binding2: None }
}

pub fn is_method(&self) -> bool {
self.method_kind.is_some()
}

pub fn set_binding2(&mut self, binding: BoundIdentifier<'a>) {
self.binding2 = Some(binding);
}
}

Expand Down Expand Up @@ -101,31 +112,130 @@ impl<'a> ClassesStack<'a> {
self.stack.last_mut()
}

/// Lookup details of private property referred to by `ident`.
pub fn find_private_prop<'b>(
pub fn lookup_private_prop<
'b,
Ret,
RetFn: Fn(&'b PrivateProp<'a>, &'b mut ClassBindings<'a>, bool) -> Ret,
>(
&'b mut self,
ident: &PrivateIdentifier<'a>,
) -> ResolvedPrivateProp<'a, 'b> {
ret_fn: RetFn,
) -> Ret {
// Check for binding in closest class first, then enclosing classes.
// We skip the first, because this is a `NonEmptyStack` with dummy first entry.
// TODO: Check there are tests for bindings in enclosing classes.
for class in self.stack[1..].iter_mut().rev() {
if let Some(private_props) = &mut class.private_props {
if let Some(prop) = private_props.get(&ident.name) {
return ResolvedPrivateProp {
prop_binding: &prop.binding,
class_bindings: &mut class.bindings,
is_static: prop.is_static,
is_method: prop.is_method,
is_accessor: prop.is_accessor,
is_declaration: class.is_declaration,
};
return ret_fn(prop, &mut class.bindings, class.is_declaration);
}
}
}

unreachable!();
}

/// Lookup details of private property referred to by `ident`.
pub fn find_private_prop<'b>(
&'b mut self,
ident: &PrivateIdentifier<'a>,
is_assignment: bool,
) -> ResolvedPrivateProp<'a, 'b> {
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
let prop_binding = match (is_assignment, prop.method_kind) {
(false, Some(MethodDefinitionKind::Set))
| (true, Some(MethodDefinitionKind::Get)) => {
if let Some(binding2) = &prop.binding2 {
binding2
} else {
// TODO: Warn there is no setter method
&prop.binding
}
}
_ => &prop.binding,
};

ResolvedPrivateProp {
prop_binding,
class_bindings,
is_static: prop.is_static,
is_method: prop.method_kind.is_some(),
is_accessor: prop.is_accessor
|| prop.method_kind.is_some_and(|kind| kind.is_accessor()),
is_declaration,
}
})
}

/// Lookup details of private property referred to by `ident`.
pub fn find_private_prop2<'b>(
&'b mut self,
ident: &PrivateIdentifier<'a>,
) -> ResolvedPrivateProp2<'a, 'b> {
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
let (get_binding, set_binding) = match prop.method_kind {
Some(MethodDefinitionKind::Set) => (prop.binding2.as_ref(), Some(&prop.binding)),
Some(_) => (Some(&prop.binding), prop.binding2.as_ref()),
_ => (Some(&prop.binding), Some(&prop.binding)),
};
ResolvedPrivateProp2 {
set_binding,
get_binding,
class_bindings,
is_static: prop.is_static,
is_method: prop.method_kind.is_some(),
is_accessor: prop.is_accessor
|| prop.method_kind.is_some_and(|kind| kind.is_accessor()),
is_declaration,
}
})
}

/// Lookup details of private property referred to by `ident`.
pub fn find_readable_private_prop<'b>(
&'b mut self,
ident: &PrivateIdentifier<'a>,
) -> Option<ResolvedPrivateProp<'a, 'b>> {
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
let prop_binding = if matches!(prop.method_kind, Some(MethodDefinitionKind::Set)) {
prop.binding2.as_ref()
} else {
Some(&prop.binding)
};
prop_binding.map(|prop_binding| ResolvedPrivateProp {
prop_binding,
class_bindings,
is_static: prop.is_static,
is_method: prop.is_method(),
is_accessor: prop.is_accessor
|| prop.method_kind.is_some_and(|kind| kind.is_accessor()),
is_declaration,
})
})
}

/// Lookup details of private property referred to by `ident`.
pub fn find_writeable_private_prop<'b>(
&'b mut self,
ident: &PrivateIdentifier<'a>,
) -> Option<ResolvedPrivateProp<'a, 'b>> {
self.lookup_private_prop(ident, move |prop, class_bindings, is_declaration| {
let prop_binding = if matches!(prop.method_kind, Some(MethodDefinitionKind::Set) | None)
{
Some(&prop.binding)
} else {
prop.binding2.as_ref()
};
prop_binding.map(|prop_binding| ResolvedPrivateProp {
prop_binding,
class_bindings,
is_static: prop.is_static,
is_method: prop.is_method(),
is_accessor: prop.is_accessor
|| prop.method_kind.is_some_and(|kind| kind.is_accessor()),
is_declaration,
})
})
}
}

/// Details of a private property resolved for a private field.
Expand All @@ -138,9 +248,31 @@ pub(super) struct ResolvedPrivateProp<'a, 'b> {
pub class_bindings: &'b mut ClassBindings<'a>,
/// `true` if is a static property
pub is_static: bool,
/// `true` if is a private method
/// `true` if is a private method or accessor property
pub is_method: bool,
/// `true` if is a private accessor property or [`PrivateProp::method_kind`] is
/// `Some(MethodDefinitionKind::Get)` or `Some(MethodDefinitionKind::Set)`
pub is_accessor: bool,
/// `true` if class which defines this property is a class declaration
pub is_declaration: bool,
}

/// Details of a private property resolved for a private field.
///
/// This is the return value of [`ClassesStack::find_private_prop`].
pub(super) struct ResolvedPrivateProp2<'a, 'b> {
/// Binding for temp var representing the property or setter method
pub set_binding: Option<&'b BoundIdentifier<'a>>,
/// Binding for temp var representing the property or getter method
pub get_binding: Option<&'b BoundIdentifier<'a>>,
/// Bindings for class name and temp var for class
pub class_bindings: &'b mut ClassBindings<'a>,
/// `true` if is a static property
pub is_static: bool,
/// `true` if is a private method or accessor property
pub is_method: bool,
/// `true` if is a private accessor property
/// `true` if is a private accessor property or [`PrivateProp::method_kind`] is
/// `Some(MethodDefinitionKind::Get)` or `Some(MethodDefinitionKind::Set)`
pub is_accessor: bool,
/// `true` if class which defines this property is a class declaration
pub is_declaration: bool,
Expand Down
Loading

0 comments on commit 63234f5

Please sign in to comment.