oxc-project / oxc-project/backlog
AST: Make Property Key and Object Property Shapes Explicit
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-
Initkind; - 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
BindingIdentifierand an optional initializer; its key is derived. - A key-value object property is always
kind: Initand 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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