Skip to content

Commit

Permalink
fix(transpiler): Fix non-inlined nested namespaces (oven-sh#12386)
Browse files Browse the repository at this point in the history
  • Loading branch information
paperclover authored Jul 6, 2024
1 parent 749c51d commit 57d2290
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 52 deletions.
155 changes: 103 additions & 52 deletions src/js_parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5306,8 +5306,13 @@ fn NewParser_(
will_wrap_module_in_try_catch_for_using: bool = false,

const RecentlyVisitedTSNamespace = struct {
ref: Ref = Ref.None,
data: ?*js_ast.TSNamespaceMemberMap = null,
expr: Expr.Data = Expr.empty.data,
map: ?*js_ast.TSNamespaceMemberMap = null,

const ExpressionData = union(enum) {
ref: Ref,
ptr: *E.Dot,
};
};

/// use this instead of checking p.source.index
Expand Down Expand Up @@ -6127,16 +6132,17 @@ fn NewParser_(
p.symbols.items[ref.inner_index].original_name,
),

.namespace => |data| {
p.ts_namespace = .{
.ref = ref,
.data = data,
};
return p.newExpr(E.Dot{
.namespace => |map| {
const expr = p.newExpr(E.Dot{
.target = p.newExpr(E.Identifier.init(ns_alias.namespace_ref), loc),
.name = ns_alias.alias,
.name_loc = loc,
}, loc);
p.ts_namespace = .{
.expr = expr.data,
.map = map,
};
return expr;
},

else => {},
Expand Down Expand Up @@ -6174,15 +6180,18 @@ fn NewParser_(
p.symbols.items[ref.inner_index].original_name,
),

.namespace => |data| {
p.ts_namespace = .{
.ref = ref,
.data = data,
};
return .{
.namespace => |map| {
const expr: Expr = .{
.data = .{ .e_identifier = ident },
.loc = loc,
};

p.ts_namespace = .{
.expr = expr.data,
.map = map,
};

return expr;
},

else => {},
Expand All @@ -6194,11 +6203,19 @@ fn NewParser_(
const name = p.symbols.items[ref.innerIndex()].original_name;

p.recordUsage(ns_ref);
return p.newExpr(E.Dot{
const prop = p.newExpr(E.Dot{
.target = p.newExpr(E.Identifier.init(ns_ref), loc),
.name = name,
.name_loc = loc,
}, loc);

if (p.ts_namespace.expr == .e_identifier and
p.ts_namespace.expr.e_identifier.ref.eql(ident.ref))
{
p.ts_namespace.expr = prop.data;
}

return prop;
}
}

Expand Down Expand Up @@ -18881,41 +18898,12 @@ fn NewParser_(
}

// Handle references to namespaces or namespace members
if (id.ref.eql(p.ts_namespace.ref) and
if (p.ts_namespace.expr == .e_identifier and
id.ref.eql(p.ts_namespace.expr.e_identifier.ref) and
identifier_opts.assign_target == .none and
!identifier_opts.is_delete_target)
{
if (p.ts_namespace.data.?.get(name)) |value| {
switch (value.data) {
.enum_number => |num| {
p.ignoreUsageOfIdentifierInDotChain(target);
return p.wrapInlinedEnum(
.{ .loc = loc, .data = .{ .e_number = .{ .value = num } } },
name,
);
},

.enum_string => |str| {
p.ignoreUsageOfIdentifierInDotChain(target);
return p.wrapInlinedEnum(
.{ .loc = loc, .data = .{ .e_string = str } },
name,
);
},

.namespace => |data| {
p.ts_namespace = .{
.ref = id.ref,
.data = data,
};
return .{
.loc = loc,
.data = .{ .e_identifier = id },
};
},
else => {},
}
}
return p.maybeRewritePropertyAccessForNamespace(name, &target, loc, name_loc);
}
},
// TODO: e_inlined_enum -> .e_string -> "length" should inline the length
Expand Down Expand Up @@ -18977,12 +18965,9 @@ fn NewParser_(
// Symbol uses due to a property access off of an imported symbol are tracked
// specially. This lets us do tree shaking for cross-file TypeScript enums.
if (p.options.bundle and !p.is_control_flow_dead) {
const i = p.symbol_uses.getIndex(id.ref).?;
const use = &p.symbol_uses.values()[i];
const use = p.symbol_uses.getPtr(id.ref).?;
use.count_estimate -= 1;
if (use.count_estimate == 0) {
// p.symbol_uses.swapRemoveAt(i);
}
// note: this use is not removed as we assume it exists later

// Add a special symbol use instead
const gop = p.import_symbol_property_uses.getOrPutValue(
Expand All @@ -18998,12 +18983,78 @@ fn NewParser_(
inner_use.value_ptr.count_estimate += 1;
}
},
inline .e_dot, .e_index => |data, tag| {
if (p.ts_namespace.expr == tag and
data == @field(p.ts_namespace.expr, @tagName(tag)) and
identifier_opts.assign_target == .none and
!identifier_opts.is_delete_target)
{
return p.maybeRewritePropertyAccessForNamespace(name, &target, loc, name_loc);
}
},
else => {},
}

return null;
}

fn maybeRewritePropertyAccessForNamespace(
p: *P,
name: string,
target: *const Expr,
loc: logger.Loc,
name_loc: logger.Loc,
) ?Expr {
if (p.ts_namespace.map.?.get(name)) |value| {
switch (value.data) {
.enum_number => |num| {
p.ignoreUsageOfIdentifierInDotChain(target.*);
return p.wrapInlinedEnum(
.{ .loc = loc, .data = .{ .e_number = .{ .value = num } } },
name,
);
},

.enum_string => |str| {
p.ignoreUsageOfIdentifierInDotChain(target.*);
return p.wrapInlinedEnum(
.{ .loc = loc, .data = .{ .e_string = str } },
name,
);
},

.namespace => |namespace| {
// If this isn't a constant, return a clone of this property access
// but with the namespace member data associated with it so that
// more property accesses off of this property access are recognized.
const expr = if (js_lexer.isIdentifier(name))
p.newExpr(E.Dot{
.target = target.*,
.name = name,
.name_loc = name_loc,
}, loc)
else
p.newExpr(E.Dot{
.target = target.*,
.name = name,
.name_loc = name_loc,
}, loc);

p.ts_namespace = .{
.expr = expr.data,
.map = namespace,
};

return expr;
},

else => {},
}
}

return null;
}

pub fn ignoreUsage(p: *P, ref: Ref) void {
if (!p.is_control_flow_dead and !p.is_revisit_for_substitution) {
if (comptime Environment.allow_assert) assert(@as(usize, ref.innerIndex()) < p.symbols.items.len);
Expand Down
50 changes: 50 additions & 0 deletions test/bundler/bundler_regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,54 @@ describe("bundler", () => {
},
entryPointsRaw: ["test/entry.ts", "--external", "*"],
});

itBundled("regression/NamespaceTracking#12337", {
files: {
"/entry.ts": /* ts */ `
(0, eval)('globalThis.ca' + 'pture = () => {};')
export namespace Test {
export function anInstance(): Test {
return {
level1: {
level2: {
level3: Level1.Level2.Level3.anInstance(),
}
},
}
}
export namespace Level1 {
export namespace Level2 {
export function anInstance(): Level2 {
return {
level3: Level3.anInstance(),
}
}
export enum Enum {
Value = 1,
}
export namespace Level3 {
export type Value = Level3['value']
export function anInstance(): Level3 {
return {
value: 'Hello, World!',
}
}
capture(Enum.Value);
}
}
capture(Level2.Enum.Value);
}
}
if(Test.anInstance().level1.level2.level3.value !== 'Hello, World!')
throw new Error('fail')
capture(Test.Level1.Level2.Enum.Value);
`,
},
run: true,
capture: ["1 /* Value */", "1 /* Value */", "1 /* Value */"],
});
});

0 comments on commit 57d2290

Please sign in to comment.