oxc-project / oxc-project/backlog

AST: Encode TypeScript Predicate Forms as an Enum

Open
#235 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

Replace the independent asserts boolean and optional type_annotation on TSTypePredicate with an enum representing ordinary type predicates and assertion predicates.

An ordinary predicate always requires an is Type annotation. An assertion predicate may either end after its parameter or include is Type. The Rust AST should encode those relationships while preserving ESTree's flat asserts and nullable typeAnnotation fields.

Motivation

TypeScript supports three predicate forms:

function isString(value: unknown): value is string;
function assertPresent(value: unknown): asserts value;
function assertString(value: unknown): asserts value is string;

The current AST stores the modifier and annotation independently:

pub struct TSTypePredicate<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub parameter_name: TSTypePredicateName<'a>,
    pub asserts: bool,
    pub type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
}

This represents four combinations. Three correspond to valid syntax, but the fourth does not:

asserts == false && type_annotation == None

That state represents a plain parameter name such as value, which is a type reference rather than a type predicate. It nevertheless causes codegen and the formatter to emit value as though it were a complete predicate.

Consumers must interpret the two fields together. Codegen and the formatter independently print asserts when the boolean is set and is Type when the annotation is present. The legacy decorator metadata transform uses the boolean to decide whether a predicate has a void or boolean runtime design type.

The parser already knows the grammatical distinction:

  • <name> is is recognized as a predicate prefix and always followed by parse_ts_type();
  • asserts <name> is parsed as an assertion predicate, with an optional is Type suffix;
  • a bare name is parsed as a type reference, not a TSTypePredicate.

That invariant is discarded when all three forms are lowered into the same independent fields.

Current AST Shape

pub struct TSTypePredicate<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub parameter_name: TSTypePredicateName<'a>,
    pub asserts: bool,
    pub type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
}

pub enum TSTypePredicateName<'a> {
    Identifier(Box<'a, IdentifierName<'a>>) = 0,
    This(Box<'a, TSThisType>) = 1,
}

parameter_name is shared by every valid form and may be either an identifier name or this.

Proposed AST Shape

Group the modifier and annotation into the two grammatical predicate categories:

pub struct TSTypePredicate<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub parameter_name: TSTypePredicateName<'a>,
    pub kind: TSTypePredicateKind<'a>,
}

pub enum TSTypePredicateKind<'a> {
    Is(Box<'a, TSTypeAnnotation<'a>>) = 0,
    Asserts(Option<Box<'a, TSTypeAnnotation<'a>>>) = 1,
}

Is(type_annotation) represents <name> is Type. Its annotation is required.
Asserts(None) represents asserts <name>, while Asserts(Some(type_annotation)) represents
asserts <name> is Type.

The enum is a grouping detail, not a new ESTree or semantic node. The annotation should retain its existing traversal position and TSTypePredicate ancestor identity.

The parser can construct the variants at the point where it already distinguishes the grammar:

// `<name> is Type`
TSTypePredicateKind::Is(type_annotation)

// `asserts <name>` or `asserts <name> is Type`
TSTypePredicateKind::Asserts(type_annotation)

For malformed input such as value is or asserts value is, the parser should continue to invoke the required type parser after consuming is. If type parsing produces a recovered or dummy TSTypeAnnotation, it can inhabit the corresponding enum variant. If parsing is fatal, recovery should proceed without constructing an annotation-less Is predicate. Do not add an optional
annotation to Is solely for error recovery.

Syntax Mapping

Source form Proposed representation
value is string Is(string)
this is Ready Is(Ready) with parameter_name: This
asserts value Asserts(None)
asserts value is string Asserts(Some(string))
asserts this Asserts(None) with parameter_name: This
value A type reference, not representable as TSTypePredicate
value is Invalid input; diagnose and recover with a type or without constructing the predicate

Guaranteed Invariants

  • A non-assertion predicate always contains an is type annotation.
  • An assertion predicate may omit its type annotation.
  • A plain parameter name with neither asserts nor is Type is not representable as a
    TSTypePredicate.
  • Assertion state and annotation requirements cannot disagree.
  • Codegen and the formatter cannot emit a bare parameter name from a valid TSTypePredicate.

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 at the parser entry points that construct TSTypePredicate and trace its uses in codegen, the formatter, and the legacy decorator metadata transform. Replace the independent fields with the proposed Is and Asserts variants while preserving annotation traversal and ESTree output. Verify all three valid predicate forms and malformed is input retain the stated invariants.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.