oxc-project / oxc-project/backlog

AST: Make Property Key and Object Property Shapes Explicit

Open
#213 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
7
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent: oxc-project/backlog#210

Summary

Distinguish static property names from computed property expressions in PropertyKey, making the computed boolean derivable from the key variant.

Also replace the independent kind, method, and shorthand state on ObjectProperty with an enum representing shorthand, key-value, and method properties. Apply the same shorthand split to BindingProperty so shorthand object literals and object patterns store their semantic identifier only once.

Motivation

PropertyKey currently contains two explicitly static variants and inherits every Expression variant:

pub enum PropertyKey<'a> {
    StaticIdentifier(Box<'a, IdentifierName<'a>>) = 64,
    PrivateIdentifier(Box<'a, PrivateIdentifier<'a>>) = 65,

    INHERIT(Expression<'a>),
}

Whether an inherited expression is a literal property name or a computed key is stored separately as computed: bool on each owning node. For example, these keys have the same inherited StringLiteral variant and can only be distinguished by the boolean:

({ "a": 1 });
({ ["a"]: 1 });

This allows contradictory states such as an arbitrary expression marked as non-computed, a static identifier marked as computed, or a private identifier marked as computed.

ObjectProperty has a second set of unenforced relationships:

pub struct ObjectProperty<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub kind: PropertyKind,
    pub key: PropertyKey<'a>,
    pub value: Expression<'a>,
    pub method: bool,
    pub shorthand: bool,
    pub computed: bool,
}

The fields permit invalid combinations, including:

  • a shorthand property with a computed key or non-Init kind;
  • a shorthand property whose key and value do not match;
  • a method or accessor whose value is not a function;
  • a key-value property with method: true;
  • a property simultaneously marked as a method and shorthand.

Consumers must interpret these fields together, while transformations must update coupled fields without allowing them to disagree.

Shorthand object properties also duplicate a single source identifier as two Rust AST nodes:

({ value });
//  ^^^^^ both the static property key and the value reference

The key is a non-semantic IdentifierName, while the value is the semantic IdentifierReference. Both have the same name and source span, and shorthand: bool asserts that they must agree.

BindingProperty has the equivalent problem for object patterns:

pub struct BindingProperty<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub key: PropertyKey<'a>,
    pub value: BindingPattern<'a>,
    pub shorthand: bool,
    pub computed: bool,
}

For const { value } = object, the static key and BindingIdentifier duplicate one token. For const { value = fallback } = object, the binding is nested inside an AssignmentPattern, while the key is still duplicated.

Assignment targets already use a stricter shape:

pub struct AssignmentTargetPropertyIdentifier<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub binding: IdentifierReference<'a>,
    pub init: Option<Expression<'a>>,
}

Its ESTree serializer derives both key and value from one identifier. Object literal and binding shorthand properties should follow the same model.

Proposed AST Shape

Add explicit static literal variants. Inherited Expression variants then represent bracketed, computed property keys:

pub enum PropertyKey<'a> {
    StaticIdentifier(Box<'a, IdentifierName<'a>>) = 64,
    StaticStringLiteral(Box<'a, StringLiteral<'a>>) = 65,
    StaticNumericLiteral(Box<'a, NumericLiteral<'a>>) = 66,
    StaticBigIntLiteral(Box<'a, BigIntLiteral<'a>>) = 67,
    PrivateIdentifier(Box<'a, PrivateIdentifier<'a>>) = 68,

    // Computed property keys.
    INHERIT(Expression<'a>),
}

The computed state is now derived:

impl PropertyKey<'_> {
    pub fn is_computed(&self) -> bool {
        self.is_expression()
    }
}

Remove independent computed fields from nodes that own a PropertyKey and derive the public value from the key variant.

Represent the valid object-property forms with an enum:

pub struct ObjectProperty<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub variant: ObjectPropertyVariant<'a>,
}

pub enum ObjectPropertyVariant<'a> {
    Shorthand {
        identifier: IdentifierReference<'a>,
    },
    KeyValue {
        key: PropertyKey<'a>,
        value: Expression<'a>,
    },
    Method {
        kind: PropertyKind,
        key: PropertyKey<'a>,
        value: Box<'a, Function<'a>>,
    },
}

The enum is conceptual; its exact representation may use boxed structs if required by AST codegen.

Shorthand::identifier is the semantic value reference. The non-semantic ESTree key is derived from the same identifier during serialization rather than stored as a second Rust AST node.

Split binding properties in the same way:

pub struct BindingProperty<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub variant: BindingPropertyVariant<'a>,
}

pub enum BindingPropertyVariant<'a> {
    Shorthand {
        binding: BindingIdentifier<'a>,
        init: Option<Expression<'a>>,
    },
    KeyValue {
        key: PropertyKey<'a>,
        value: BindingPattern<'a>,
    },
}

The shorthand binding variant represents both { value } and { value = fallback }. It does not contain a PropertyKey; the static key is derived from the original binding name.

For ESTree, method is derived as true only for Method { kind: Init, .. }. Getters and setters retain kind: Get | Set and method: false.

ESTree continues to expose separate key and value nodes for shorthand properties. The serializer creates both views from the single Rust identifier or binding.

Codegen must retain the behavior already used by AssignmentTargetPropertyIdentifier: if mangling changes the emitted identifier name, expand the shorthand while preserving the original property name:

// `value` is mangled to `a`
({ value: a });
const { value: a } = object;

ObjectPropertyVariant::Shorthand also keeps { __proto__ } structurally distinct from KeyValue with an uncomputed __proto__ key. The former creates a normal own property; the latter can invoke the Annex B prototype-setter behavior.

Syntax Mapping

Source form Proposed representation
{ a: 1 } KeyValue with StaticIdentifier
{ "a": 1 } KeyValue with StaticStringLiteral
{ 1: "one" } KeyValue with StaticNumericLiteral
{ [a]: 1 } KeyValue with an inherited Identifier expression
{ ["a"]: 1 } KeyValue with an inherited StringLiteral expression
{ a } Object Shorthand with one IdentifierReference
{ a() {} } Method { kind: Init, .. }
{ get a() {} } Method { kind: Get, .. }
class C { #a = 1 } PrivateIdentifier
const { a } = value Binding Shorthand with one BindingIdentifier
const { a = fallback } = value Binding Shorthand with a binding and init
const { key: local } = value Binding KeyValue

Guaranteed Invariants

  • Static and computed property syntax cannot share the same key variant.
  • The computed state cannot disagree with the key.
  • A shorthand object property contains one semantic IdentifierReference; its key is derived.
  • A shorthand binding property contains one BindingIdentifier and an optional initializer; its key is derived.
  • A key-value object property is always kind: Init and is never a method.
  • A method, getter, or setter always contains a Function.
  • Method and shorthand state no longer require independent booleans or duplicated identifiers.
  • Shorthand and key-value forms are changed atomically rather than by synchronizing key, value, and boolean fields.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the existing AssignmentTargetPropertyIdentifier representation and its ESTree serializer, then trace the current PropertyKey, ObjectProperty, and BindingProperty definitions and their codegen paths. Done means the proposed variants enforce the listed invariants, serializers preserve ESTree output, and shorthand properties expand correctly when mangling changes an identifier.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.