oxc-project / oxc-project/backlog
AST: Encode TypeScript Predicate Forms as an Enum
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> isis recognized as a predicate prefix and always followed byparse_ts_type();asserts <name>is parsed as an assertion predicate, with an optionalis Typesuffix;- 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
istype annotation. - An assertion predicate may omit its type annotation.
- A plain parameter name with neither
assertsnoris Typeis 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
- 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 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