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

fix(transformer/typescript): enum merging when same name declared in outer scope #8691

Merged
merged 7 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/oxc_data_structures/src/stack/non_empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ impl<T> NonEmptyStack<T> {
Self { cursor: start, start, end }
}

/// Get reference to first value on stack.
#[inline]
pub fn first(&self) -> &T {
// SAFETY: All methods ensure `self.start` always points to a valid initialized `T`
unsafe { self.start.as_ref() }
}

/// Get mutable reference to first value on stack.
#[inline]
pub fn first_mut(&mut self) -> &mut T {
// SAFETY: All methods ensure `self.start` always points to a valid initialized `T`
unsafe { self.start.as_mut() }
}

/// Get reference to last value on stack.
#[inline]
pub fn last(&self) -> &T {
Expand Down
63 changes: 55 additions & 8 deletions crates/oxc_transformer/src/typescript/enum.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use rustc_hash::FxHashMap;
use std::cell::Cell;

use oxc_allocator::Vec as ArenaVec;
use oxc_ast::{ast::*, visit::walk_mut, VisitMut, NONE};
use oxc_data_structures::stack::NonEmptyStack;
use oxc_ecmascript::ToInt32;
use oxc_semantic::ScopeId;
use oxc_semantic::{ScopeFlags, ScopeId};
use oxc_span::{Atom, Span, SPAN};
use oxc_syntax::{
number::{NumberBase, ToJsString},
Expand Down Expand Up @@ -521,9 +523,9 @@ impl<'a> TypeScriptEnum<'a> {
/// ```
struct IdentifierReferenceRename<'a, 'ctx> {
enum_name: Atom<'a>,
enum_scope_id: ScopeId,
ctx: &'ctx TraverseCtx<'a>,
previous_enum_members: PrevMembers<'a>,
scope_stack: NonEmptyStack<ScopeId>,
ctx: &'ctx TraverseCtx<'a>,
}

impl<'a, 'ctx> IdentifierReferenceRename<'a, 'ctx> {
Expand All @@ -533,23 +535,68 @@ impl<'a, 'ctx> IdentifierReferenceRename<'a, 'ctx> {
previous_enum_members: PrevMembers<'a>,
ctx: &'ctx TraverseCtx<'a>,
) -> Self {
IdentifierReferenceRename { enum_name, enum_scope_id, ctx, previous_enum_members }
IdentifierReferenceRename {
enum_name,
previous_enum_members,
scope_stack: NonEmptyStack::new(enum_scope_id),
ctx,
}
}
}

impl IdentifierReferenceRename<'_, '_> {
fn should_reference_enum_member(&self, ident: &IdentifierReference<'_>) -> bool {
// Don't need to rename the identifier if it's not a member of the enum,
if !self.previous_enum_members.contains_key(&ident.name) {
return false;
};

let symbol_table = self.ctx.scoping.symbols();
let Some(symbol_id) = symbol_table.get_reference(ident.reference_id()).symbol_id() else {
// No symbol found. If the name is found in previous_enum_members,
// it must be referencing a member declared in a previous enum block: `enum Foo { A }; enum Foo { B = A }`
return self.previous_enum_members.contains_key(&ident.name);
// No symbol found, yet the name is found in previous_enum_members.
// It must be referencing a member declared in a previous enum block: `enum Foo { A }; enum Foo { B = A }`
return true;
};
symbol_table.get_scope_id(symbol_id) == self.enum_scope_id

let symbol_scope_id = symbol_table.get_scope_id(symbol_id);
// Don't need to rename the identifier when it references a nested enum member:
//
// ```ts
// enum OuterEnum {
// A = 0,
// B = () => {
// enum InnerEnum {
// A = 0,
// B = A,
// ^ This references to `InnerEnum.A` should not be renamed
// }
// return InnerEnum.B;
// }
// }
// ```
*self.scope_stack.first() == symbol_scope_id
// The resolved symbol is declared outside the enum,
// and we have checked that the name exists in previous_enum_members:
//
// ```ts
// const A = 0;
// enum Foo { A }
// enum Foo { B = A }
// ^ This should be renamed to Foo.A
// ```
|| !self.scope_stack.contains(&symbol_scope_id)
}
}

impl<'a> VisitMut<'a> for IdentifierReferenceRename<'a, '_> {
fn enter_scope(&mut self, _flags: ScopeFlags, scope_id: &Cell<Option<ScopeId>>) {
self.scope_stack.push(scope_id.get().unwrap());
}

fn leave_scope(&mut self) {
self.scope_stack.pop();
}

fn visit_expression(&mut self, expr: &mut Expression<'a>) {
match expr {
Expression::Identifier(ident) if self.should_reference_enum_member(ident) => {
Expand Down
49 changes: 48 additions & 1 deletion tasks/transform_conformance/snapshots/oxc.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,65 @@ rebuilt : ScopeId(0): []

* enum-member-reference/input.ts
Missing ReferenceId: "Foo"
Missing ReferenceId: "Merge"
Missing ReferenceId: "NestInner"
Bindings mismatch:
after transform: ScopeId(1): ["Foo", "a", "b", "c"]
rebuilt : ScopeId(1): ["Foo"]
Scope flags mismatch:
after transform: ScopeId(1): ScopeFlags(0x0)
rebuilt : ScopeId(1): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(2): ["Merge", "x"]
rebuilt : ScopeId(2): ["Merge"]
Scope flags mismatch:
after transform: ScopeId(2): ScopeFlags(0x0)
rebuilt : ScopeId(2): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(3): ["Merge", "y"]
rebuilt : ScopeId(3): ["Merge"]
Scope flags mismatch:
after transform: ScopeId(3): ScopeFlags(0x0)
rebuilt : ScopeId(3): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(4): ["NestOuter", "a", "b"]
rebuilt : ScopeId(4): ["NestOuter"]
Scope flags mismatch:
after transform: ScopeId(4): ScopeFlags(0x0)
rebuilt : ScopeId(4): ScopeFlags(Function)
Bindings mismatch:
after transform: ScopeId(6): ["NestInner", "a", "b"]
rebuilt : ScopeId(6): ["NestInner"]
Scope flags mismatch:
after transform: ScopeId(6): ScopeFlags(0x0)
rebuilt : ScopeId(6): ScopeFlags(Function)
Symbol reference IDs mismatch for "x":
after transform: SymbolId(0): [ReferenceId(2), ReferenceId(4)]
rebuilt : SymbolId(0): [ReferenceId(7)]
Symbol flags mismatch for "Foo":
after transform: SymbolId(1): SymbolFlags(RegularEnum)
rebuilt : SymbolId(1): SymbolFlags(FunctionScopedVariable)
Symbol reference IDs mismatch for "Foo":
after transform: SymbolId(5): [ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6), ReferenceId(7), ReferenceId(8), ReferenceId(9)]
after transform: SymbolId(14): [ReferenceId(8), ReferenceId(9), ReferenceId(10), ReferenceId(11), ReferenceId(12), ReferenceId(13), ReferenceId(14)]
rebuilt : SymbolId(2): [ReferenceId(0), ReferenceId(1), ReferenceId(2), ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6), ReferenceId(8)]
Symbol flags mismatch for "Merge":
after transform: SymbolId(5): SymbolFlags(RegularEnum)
rebuilt : SymbolId(3): SymbolFlags(FunctionScopedVariable)
Symbol redeclarations mismatch for "Merge":
after transform: SymbolId(5): [Span { start: 103, end: 108 }]
rebuilt : SymbolId(3): []
Symbol reference IDs mismatch for "Merge":
after transform: SymbolId(16): [ReferenceId(20), ReferenceId(21), ReferenceId(22)]
rebuilt : SymbolId(5): [ReferenceId(16), ReferenceId(17), ReferenceId(18), ReferenceId(19)]
Symbol flags mismatch for "NestOuter":
after transform: SymbolId(8): SymbolFlags(RegularEnum)
rebuilt : SymbolId(6): SymbolFlags(FunctionScopedVariable)
Symbol flags mismatch for "NestInner":
after transform: SymbolId(11): SymbolFlags(RegularEnum)
rebuilt : SymbolId(8): SymbolFlags(BlockScopedVariable)
Symbol reference IDs mismatch for "NestInner":
after transform: SymbolId(18): [ReferenceId(31), ReferenceId(32), ReferenceId(33), ReferenceId(34), ReferenceId(35)]
rebuilt : SymbolId(9): [ReferenceId(25), ReferenceId(26), ReferenceId(28), ReferenceId(29), ReferenceId(30), ReferenceId(31)]

* export-elimination/input.ts
Bindings mismatch:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@ enum Foo {
b = a,
c = b + x,
}

enum Merge { x = Math.random() }
enum Merge { y = x }

enum NestOuter {
a,
b = (() => {
enum NestInner {
a = Math.random(),
b = a
}
return NestInner.b;
})()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,23 @@ var Foo = function(Foo) {
Foo[Foo['c'] = Foo.b + x] = 'c';
return Foo;
}(Foo || {});
var Merge = function(Merge) {
Merge[Merge['x'] = Math.random()] = 'x';
return Merge;
}(Merge || {});
Merge = function(Merge) {
Merge[Merge['y'] = Merge.x] = 'y';
return Merge;
}(Merge || {});
var NestOuter = function(NestOuter) {
NestOuter[NestOuter['a'] = 0] = 'a';
NestOuter[NestOuter['b'] = (() => {
let NestInner = function(NestInner) {
NestInner[NestInner['a'] = Math.random()] = 'a';
NestInner[NestInner['b'] = NestInner.a] = 'b';
return NestInner;
}({});
return NestInner.b;
})()] = 'b';
return NestOuter;
}(NestOuter || {});
Loading