Files
oxc-project__oxc/tasks/track_memory_allocations/allocs_semantic.yaml
camc314 a33788e13e feat(ast)!: group class heritage into ClassHeritage (#25360)
## Summary

- Groups `Class::super_class` and `Class::super_type_arguments` into `Class::heritage`.
- Introduces `ClassHeritage` containing the superclass expression and its optional TypeScript type arguments.
- Makes superclass type arguments without a superclass expression unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct Class<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub r#type: ClassType,
    pub decorators: Vec<'a, Decorator<'a>>,
    pub id: Option<BindingIdentifier<'a>>,
    #[ts]
    pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
    pub super_class: Option<Expression<'a>>,
    #[ts]
    pub super_type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
    #[ts]
    pub implements: Vec<'a, TSClassImplements<'a>>,
    pub body: Box<'a, ClassBody<'a>>,
    #[ts]
    pub r#abstract: bool,
    #[ts]
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}
```

to:

```rust
pub struct Class<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub r#type: ClassType,
    pub decorators: Vec<'a, Decorator<'a>>,
    pub id: Option<BindingIdentifier<'a>>,
    #[ts]
    pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
    pub heritage: Option<ClassHeritage<'a>>,
    #[ts]
    pub implements: Vec<'a, TSClassImplements<'a>>,
    pub body: Box<'a, ClassBody<'a>>,
    #[ts]
    pub r#abstract: bool,
    #[ts]
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}

pub struct ClassHeritage<'a> {
    pub expression: Expression<'a>,
    #[ts]
    pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
```

## Why?

ECMAScript defines the complete `extends` clause as `ClassHeritage`:

```text
ClassHeritage : extends LeftHandSideExpression
```

TypeScript additionally permits type arguments on the heritage expression:

```ts
class Derived extends Base<T> {}
```

Previously, the AST stored the expression and type arguments independently:

```text
Class {
    super_class: Some(Base),
    super_type_arguments: Some(<T>),
    ...
}
```

This allowed an invalid state to be constructed:

```text
Class {
    super_class: None,
    super_type_arguments: Some(<T>),
    ...
}
```

Superclass type arguments cannot exist without an `extends` expression.

The parser already treated these fields as a pair. It parsed a class heritage entry and then split its expression and type arguments into two independent fields. Builders and transforms therefore had to preserve their relationship manually.

Grouping them into `ClassHeritage` makes the syntactic dependency explicit and prevents consumers from observing or constructing contradictory class heritage.

## Class heritage forms

A class without an `extends` clause has no heritage:

```js
class Base {}
```

```text
Class {
    heritage: None,
    ...
}
```

A class extending an expression has heritage without type arguments:

```js
class Derived extends Base {}
```

```text
Class {
    heritage: Some(ClassHeritage {
        expression: Base,
        type_arguments: None,
    }),
    ...
}
```

TypeScript type arguments belong to the same heritage structure:

```ts
class Derived extends Base<T> {}
```

```text
Class {
    heritage: Some(ClassHeritage {
        expression: Base,
        type_arguments: Some(<T>),
    }),
    ...
}
```

The heritage expression may be any valid class heritage expression:

```js
class Derived extends mixin(Base) {}
class NullDerived extends null {}
```

## How to migrate

Code that previously accessed the superclass expression directly:

```rust
if let Some(super_class) = &class.super_class {
    // ...
}
```

can use the compatibility accessor:

```rust
if let Some(super_class) = class.super_class() {
    // ...
}
```

Code that needs both parts should access the grouped heritage:

```rust
if let Some(heritage) = &class.heritage {
    let expression = &heritage.expression;
    let type_arguments = heritage.type_arguments.as_deref();
}
```

Mutating consumers should update the nested fields:

```rust
if let Some(heritage) = &mut class.heritage {
    heritage.type_arguments = None;
}
```

Common migrations:

| Previous representation | New representation |
| --- | --- |
| `class.super_class` | `class.heritage.as_ref().map(\|h\| &h.expression)` or `class.super_class()` |
| `class.super_type_arguments` | `class.heritage.as_ref()?.type_arguments` or `class.super_type_arguments()` |
| `class.super_class.is_some()` | `class.heritage.is_some()` |
| Mutate `class.super_class` | Mutate `class.heritage.expression` |
| Clear `class.super_type_arguments` | Clear `class.heritage.type_arguments` |
| Pass superclass and type arguments separately to a `Class` builder | Construct and pass `ClassHeritage` |
| Clone both fields independently | Clone `class.heritage` |

```ts
superClass: Expression | null;
superTypeArguments?: TSTypeParameterInstantiation | null;
```

No `heritage` property or `ClassHeritage` node is added to the ESTree output.

fixes https://github.com/oxc-project/backlog/issues/216
2026-08-07 14:18:59 +00:00

77 lines
1.7 KiB
YAML

checker.ts:
file size: 2922154 # 2.92 MB
sys allocs: 46
sys reallocs: 0
sys deallocs: 46
sys alloc bytes: 3866958 # 3.87 MB
sys peak growth: 3858362 # 3.86 MB
arena allocs: 0
arena reallocs: 0
arena size: 12923744 # 12.92 MB
App.tsx:
file size: 415342 # 415.34 kB
sys allocs: 7
sys reallocs: 0
sys deallocs: 7
sys alloc bytes: 360848 # 360.85 kB
sys peak growth: 360848 # 360.85 kB
arena allocs: 0
arena reallocs: 0
arena size: 2230856 # 2.23 MB
RadixUIAdoptionSection.jsx:
file size: 2520 # 2.52 kB
sys allocs: 7
sys reallocs: 0
sys deallocs: 7
sys alloc bytes: 2748 # 2.75 kB
sys peak growth: 2748 # 2.75 kB
arena allocs: 0
arena reallocs: 0
arena size: 21200 # 21.20 kB
pdf.mjs:
file size: 567296 # 567.30 kB
sys allocs: 12
sys reallocs: 0
sys deallocs: 12
sys alloc bytes: 833710 # 833.71 kB
sys peak growth: 832762 # 832.76 kB
arena allocs: 0
arena reallocs: 0
arena size: 4536712 # 4.54 MB
antd.js:
file size: 6686316 # 6.69 MB
sys allocs: 24
sys reallocs: 0
sys deallocs: 24
sys alloc bytes: 5512934 # 5.51 MB
sys peak growth: 5441278 # 5.44 MB
arena allocs: 0
arena reallocs: 0
arena size: 29421920 # 29.42 MB
binder.ts:
file size: 193077 # 193.08 kB
sys allocs: 14
sys reallocs: 0
sys deallocs: 14
sys alloc bytes: 247026 # 247.03 kB
sys peak growth: 246886 # 246.89 kB
arena allocs: 0
arena reallocs: 0
arena size: 916328 # 916.33 kB
kitchen-sink.tsx:
file size: 732898 # 732.90 kB
sys allocs: 33
sys reallocs: 0
sys deallocs: 33
sys alloc bytes: 1103942 # 1.10 MB
sys peak growth: 1101954 # 1.10 MB
arena allocs: 0
arena reallocs: 0
arena size: 6440104 # 6.44 MB