Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(transformer): enable class-properties plugin #7750

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
18 changes: 18 additions & 0 deletions crates/oxc_ast/src/ast_impl/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,24 @@ impl<'a> Expression<'a> {
}
}

#[allow(missing_docs)]
#[must_use]
pub fn into_inner_expression(self) -> Expression<'a> {
let mut expr = self;
loop {
expr = match expr {
Expression::ParenthesizedExpression(e) => e.unbox().expression,
Expression::TSAsExpression(e) => e.unbox().expression,
Expression::TSSatisfiesExpression(e) => e.unbox().expression,
Expression::TSInstantiationExpression(e) => e.unbox().expression,
Expression::TSNonNullExpression(e) => e.unbox().expression,
Expression::TSTypeAssertion(e) => e.unbox().expression,
_ => break,
};
}
expr
}

#[allow(missing_docs)]
pub fn get_inner_expression(&self) -> &Expression<'a> {
let mut expr = self;
Expand Down
23 changes: 23 additions & 0 deletions crates/oxc_syntax/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ use serde::{Serialize, Serializer};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SymbolId(NonMaxU32);

impl SymbolId {
/// Create `SymbolId` from `u32`.
///
/// # Panics
/// Panics if `idx` is `u32::MAX`.
pub const fn new(idx: u32) -> Self {
if let Some(idx) = NonMaxU32::new(idx) {
return Self(idx);
}
panic!();
}

/// Create `SymbolId` from `u32` unchecked.
///
/// # SAFETY
/// `idx` must not be `u32::MAX`.
#[allow(clippy::missing_safety_doc, clippy::unnecessary_safety_comment)]
pub const unsafe fn new_unchecked(idx: u32) -> Self {
// SAFETY: Caller must ensure `idx` is not `u32::MAX`
Self(NonMaxU32::new_unchecked(idx))
}
}

impl Idx for SymbolId {
#[allow(clippy::cast_possible_truncation)]
fn from_usize(idx: usize) -> Self {
Expand Down
74 changes: 57 additions & 17 deletions crates/oxc_transformer/src/es2022/class_properties/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,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 { binding, is_static: prop.r#static },
PrivateProp::new(binding, prop.r#static, false, false),
);
}

Expand All @@ -112,21 +112,43 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}
}
ClassElement::MethodDefinition(method) => {
if method.kind == MethodDefinitionKind::Constructor
&& method.value.body.is_some()
{
constructor = Some(method);
if method.kind == MethodDefinitionKind::Constructor {
if method.value.body.is_some() {
constructor = Some(method);
}
} else if let PropertyKey::PrivateIdentifier(ident) = &method.key {
let dummy_binding = BoundIdentifier::new(Atom::empty(), SymbolId::new(0));
private_props.insert(
ident.name.clone(),
PrivateProp::new(dummy_binding, method.r#static, true, false),
);
}
}
ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {
// TODO: Need to handle these?
ClassElement::AccessorProperty(prop) => {
// TODO: Not sure what we should do here.
// Only added this to prevent panics in TS conformance tests.
if let PropertyKey::PrivateIdentifier(ident) = &prop.key {
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),
);
}
}
ClassElement::TSIndexSignature(_) => {
// TODO: Need to handle this?
}
}
}

// Exit if nothing to transform
if instance_prop_count == 0 && !has_static_prop && !has_static_block {
self.classes_stack.push(ClassDetails::empty(is_declaration));
self.classes_stack.push(ClassDetails {
is_declaration,
is_transform_required: false,
private_props: if private_props.is_empty() { None } else { Some(private_props) },
bindings: ClassBindings::dummy(),
});
return;
}

Expand Down Expand Up @@ -310,7 +332,7 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
}

// Leave class expressions to `transform_class_expression_on_exit`
let class_details = self.current_class_mut();
let class_details = self.current_class();
if !class_details.is_declaration {
return;
}
Expand Down Expand Up @@ -381,12 +403,17 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {

if let Some(private_props) = &class_details.private_props {
if self.private_fields_as_properties {
// TODO: Only call `insert_many_before` if some private *props*
self.ctx.statement_injector.insert_many_before(
&stmt_address,
private_props.iter().map(|(name, prop)| {
private_props.iter().filter_map(|(name, prop)| {
if prop.is_method || prop.is_accessor {
return None;
}

// `var _prop = _classPrivateFieldLooseKey("prop");`
let value = Self::create_private_prop_key_loose(name, self.ctx, ctx);
create_variable_declaration(&prop.binding, value, ctx)
Some(create_variable_declaration(&prop.binding, value, ctx))
}),
);
} else {
Expand All @@ -395,7 +422,7 @@ 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 {
if prop.is_static || prop.is_method || prop.is_accessor {
return None;
}

Expand Down Expand Up @@ -482,8 +509,8 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
// which can be very far above the class in AST, when it's a `var`.
// Maybe for now only add class name if it doesn't shadow a var used within class?

// TODO: Deduct static private props from `expr_count`.
// Or maybe should store count and increment it when create private static props?
// TODO: Deduct static private props and private methods from `expr_count`.
// Or maybe should store count and increment it when create private static props or private methods?
// They're probably pretty rare, so it'll be rarely used.
let class_details = self.classes_stack.last();

Expand Down Expand Up @@ -511,17 +538,25 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
// `c = class C { #x = 1; static y = 2; }` -> `var _C, _x;`
// TODO(improve-on-babel): Simplify this.
if self.private_fields_as_properties {
exprs.extend(private_props.iter().map(|(name, prop)| {
exprs.extend(private_props.iter().filter_map(|(name, prop)| {
if prop.is_method || prop.is_accessor {
return None;
}

// Insert `var _prop;` declaration
self.ctx.var_declarations.insert_var(&prop.binding, ctx);

// `_prop = _classPrivateFieldLooseKey("prop")`
let value = Self::create_private_prop_key_loose(name, self.ctx, ctx);
create_assignment(&prop.binding, value, ctx)
Some(create_assignment(&prop.binding, value, ctx))
}));
} else {
let mut weakmap_symbol_id = None;
exprs.extend(private_props.values().filter_map(|prop| {
if prop.is_method || prop.is_accessor {
return None;
}

// Insert `var _prop;` declaration
self.ctx.var_declarations.insert_var(&prop.binding, ctx);

Expand All @@ -540,14 +575,14 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
exprs.extend(self.insert_before.drain(..));

// Insert class + static property assignments + static blocks
let class_expr = ctx.ast.move_expression(expr);
if let Some(binding) = &class_details.bindings.temp {
// Insert `var _Class` statement, if it wasn't already in entry phase
if !class_details.bindings.temp_var_is_created {
self.ctx.var_declarations.insert_var(binding, ctx);
}

// `_Class = class {}`
let class_expr = ctx.ast.move_expression(expr);
let assignment = create_assignment(binding, class_expr, ctx);
exprs.push(assignment);
// Add static property assignments + static blocks
Expand All @@ -564,6 +599,11 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
// -> `let C = ((x = 1), class C extends Bound {});`
exprs.extend(self.insert_after_exprs.drain(..));

if exprs.is_empty() {
return;
}

let class_expr = ctx.ast.move_expression(expr);
exprs.push(class_expr);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ pub(super) struct ClassDetails<'a> {
}

impl<'a> ClassDetails<'a> {
/// Create empty `ClassDetails`.
/// Create dummy `ClassDetails`.
///
/// Used when class needs no transform, and for dummy entry at top of `ClassesStack`.
pub fn empty(is_declaration: bool) -> Self {
/// Used for dummy entry at top of `ClassesStack`.
pub fn dummy(is_declaration: bool) -> Self {
Self {
is_declaration,
is_transform_required: false,
Expand All @@ -40,6 +40,19 @@ impl<'a> ClassDetails<'a> {
pub(super) struct PrivateProp<'a> {
pub binding: BoundIdentifier<'a>,
pub is_static: bool,
pub is_method: bool,
pub is_accessor: bool,
}

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

/// Stack of `ClassDetails`.
Expand All @@ -61,7 +74,7 @@ impl<'a> ClassesStack<'a> {
/// Create new `ClassesStack`.
pub fn new() -> Self {
// Default stack capacity is 4. That's is probably good. More than 4 nested classes is rare.
Self { stack: NonEmptyStack::new(ClassDetails::empty(false)) }
Self { stack: NonEmptyStack::new(ClassDetails::dummy(false)) }
}

/// Push an entry to stack.
Expand Down Expand Up @@ -92,24 +105,26 @@ impl<'a> ClassesStack<'a> {
pub fn find_private_prop<'b>(
&'b mut self,
ident: &PrivateIdentifier<'a>,
) -> Option<ResolvedPrivateProp<'a, 'b>> {
) -> ResolvedPrivateProp<'a, 'b> {
// 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 Some(ResolvedPrivateProp {
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,
});
};
}
}
}
// TODO: This should be unreachable. Only returning `None` because implementation is incomplete.
None

unreachable!();
}
}

Expand All @@ -123,6 +138,10 @@ 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
pub is_method: bool,
/// `true` if is a private accessor property
pub is_accessor: bool,
/// `true` if class which defines this property is a class declaration
pub is_declaration: bool,
}
Expand Down
Loading
Loading