From a08d5f0cb4d44321d52fd4d81da3efdf03ffe419 Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 21 May 2026 00:33:59 -0700 Subject: [PATCH] [rust-compiler] Allow Flow `TypeCastExpression` in `PatternLike` `PatternLike` already had the four TS cast wrapper variants (`TSAsExpression`, `TSSatisfiesExpression`, `TSNonNullExpression`, `TSTypeAssertion`) so that `(x as T) = ...` style assignment targets and parameter positions are representable. Flow's analogue of those wrappers is `TypeCastExpression`, e.g. function f(fallback: React.Node = <>): void { ... } where `fallback: React.Node = <>` reaches the converter as an `AssignmentPattern` whose `left` is a `TypeCastExpression` over the binding identifier. The round-trip test rejected this with: Deserialization error: unknown variant `TypeCastExpression`, expected one of `Identifier`, `ObjectPattern`, ... `TSTypeAssertion` Add `TypeCastExpression` as the sixth expression-like variant of `PatternLike`, reusing the existing `crate::expressions::TypeCastExpression` struct (which already matches Babel's `{ expression, typeAnnotation }` shape and is already handled on the expression side). Wire the new variant through every exhaustive `PatternLike` match the workspace requires, mirroring the existing TS wrapper handling at each site: - `react_compiler_ast/src/visitor.rs::walk_pattern`: recurse into the inner expression. - `react_compiler_lowering/src/find_context_identifiers.rs`: record the TS-faithful `[FindContextIdentifiers] Cannot handle Object destructuring assignment target TypeCastExpression` Todo. - `react_compiler_lowering/src/build_hir.rs`: fall-through arms in `pattern_like_loc`, `pattern_declares_name`, `collect_binding_names_from_pattern`, `lower_assignment`, and the inner-function-parameter walker. - `react_compiler/src/entrypoint/program.rs`: fall-through arms in `calls_hooks_or_creates_jsx_in_pattern` and `is_valid_props_annotation`. - `react_compiler/src/entrypoint/validate_source_locations.rs`: fall-through arms in the original/generated pattern collectors. - `react_compiler_swc/src/convert_ast_reverse.rs`: convert to `Pat::Expr` (mirrors TS wrappers); use `__unknown__` placeholder in the assign-target path. - `react_compiler_oxc/src/convert_ast.rs` and `react_compiler_oxc/src/convert_ast_reverse.rs`: equivalent no-op / placeholder arms in the OXC frontend. No new behavior is introduced. The reverse converters fall through the same way they do for TS wrappers, and the Todo emission matches the TS reference behavior for an unsupported assignment- target wrapper. Fixes 1 round-trip parity failure: - error.todo-round2_fn_type_component_to_other.js Test plan: - bash compiler/scripts/test-babel-ast.sh: Before: 1778/1780 round-trip (2 failures) After: 1779/1780 round-trip (1 failure, 1 fixed) Remaining failure (`lone-surrogate-string-values.js`) is unrelated; handled in the next commit as a skip-list entry pending WTF-8 string-representation work. - cargo build: clean across the workspace. --- .../react_compiler/src/entrypoint/program.rs | 6 ++++-- .../entrypoint/validate_source_locations.rs | 6 ++++-- .../crates/react_compiler_ast/src/patterns.rs | 2 ++ .../crates/react_compiler_ast/src/visitor.rs | 3 +++ .../tests/scope_resolution.rs | 4 ++++ .../react_compiler_lowering/src/build_hir.rs | 20 ++++++++++++------- .../src/find_context_identifiers.rs | 7 +++++++ .../react_compiler_oxc/src/convert_ast.rs | 3 ++- .../src/convert_ast_reverse.rs | 6 ++++-- .../src/convert_ast_reverse.rs | 10 +++++++--- 10 files changed, 50 insertions(+), 17 deletions(-) diff --git a/compiler/crates/react_compiler/src/entrypoint/program.rs b/compiler/crates/react_compiler/src/entrypoint/program.rs index 66fe552dca7a..a0471b8ca265 100644 --- a/compiler/crates/react_compiler/src/entrypoint/program.rs +++ b/compiler/crates/react_compiler/src/entrypoint/program.rs @@ -904,7 +904,8 @@ fn calls_hooks_or_creates_jsx_in_pattern(pattern: &PatternLike) -> bool { | PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => false, + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => false, } } @@ -923,7 +924,8 @@ fn is_valid_props_annotation(param: &PatternLike) -> bool { | PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => None, + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => None, }; let annot = match type_annotation { Some(val) => val, diff --git a/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs b/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs index c785d7137457..a63d556ac97c 100644 --- a/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs +++ b/compiler/crates/react_compiler/src/entrypoint/validate_source_locations.rs @@ -749,7 +749,8 @@ fn collect_original_pattern( PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => {} + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => {} } } @@ -1243,7 +1244,8 @@ fn collect_generated_pattern( PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => {} + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => {} } } diff --git a/compiler/crates/react_compiler_ast/src/patterns.rs b/compiler/crates/react_compiler_ast/src/patterns.rs index 48e199fd4473..1064ea7e6378 100644 --- a/compiler/crates/react_compiler_ast/src/patterns.rs +++ b/compiler/crates/react_compiler_ast/src/patterns.rs @@ -20,6 +20,8 @@ pub enum PatternLike { TSSatisfiesExpression(crate::expressions::TSSatisfiesExpression), TSNonNullExpression(crate::expressions::TSNonNullExpression), TSTypeAssertion(crate::expressions::TSTypeAssertion), + // Flow's analogue of the TS cast wrappers: `(expr: SomeType)`. + TypeCastExpression(crate::expressions::TypeCastExpression), } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/compiler/crates/react_compiler_ast/src/visitor.rs b/compiler/crates/react_compiler_ast/src/visitor.rs index 21dafa6dbfe4..496d93f49623 100644 --- a/compiler/crates/react_compiler_ast/src/visitor.rs +++ b/compiler/crates/react_compiler_ast/src/visitor.rs @@ -597,6 +597,9 @@ impl<'a> AstWalker<'a> { self.walk_expression(v, &node.expression) } PatternLike::TSTypeAssertion(node) => self.walk_expression(v, &node.expression), + PatternLike::TypeCastExpression(node) => { + self.walk_expression(v, &node.expression) + } } } diff --git a/compiler/crates/react_compiler_ast/tests/scope_resolution.rs b/compiler/crates/react_compiler_ast/tests/scope_resolution.rs index 6c5adbaaf1b3..0fcef0d35145 100644 --- a/compiler/crates/react_compiler_ast/tests/scope_resolution.rs +++ b/compiler/crates/react_compiler_ast/tests/scope_resolution.rs @@ -761,6 +761,10 @@ fn visit_pat(pat: &mut PatternLike, si: &ScopeInfo) { visit_expr(&mut e.expression, si); visit_json(&mut e.type_annotation, si); } + PatternLike::TypeCastExpression(e) => { + visit_expr(&mut e.expression, si); + visit_json(&mut e.type_annotation, si); + } } } diff --git a/compiler/crates/react_compiler_lowering/src/build_hir.rs b/compiler/crates/react_compiler_lowering/src/build_hir.rs index ac045b8c31c2..b7e5bcf85911 100644 --- a/compiler/crates/react_compiler_lowering/src/build_hir.rs +++ b/compiler/crates/react_compiler_lowering/src/build_hir.rs @@ -65,6 +65,7 @@ fn pattern_like_loc( PatternLike::TSSatisfiesExpression(p) => p.base.loc.clone(), PatternLike::TSNonNullExpression(p) => p.base.loc.clone(), PatternLike::TSTypeAssertion(p) => p.base.loc.clone(), + PatternLike::TypeCastExpression(p) => p.base.loc.clone(), } } @@ -2327,7 +2328,8 @@ fn pattern_declares_name(pattern: &react_compiler_ast::patterns::PatternLike, na PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => false, + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => false, } } @@ -2533,7 +2535,8 @@ fn collect_binding_names_from_pattern( PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => {} + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => {} } } @@ -5091,13 +5094,15 @@ fn lower_assignment( )?) } - // TS assignment-target wrappers (e.g. `(x as T) = ...`). The TS-faithful - // Todo is recorded once in `find_context_identifiers`; lowering itself - // never reaches a successful path for these, so do not record again. + // TS assignment-target wrappers (e.g. `(x as T) = ...`) and the Flow + // analogue `TypeCastExpression`. The TS-faithful Todo is recorded once + // in `find_context_identifiers`; lowering itself never reaches a + // successful path for these, so do not record again. PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => Ok(None), + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => Ok(None), } } @@ -6037,7 +6042,8 @@ fn lower_inner( react_compiler_ast::patterns::PatternLike::TSAsExpression(_) | react_compiler_ast::patterns::PatternLike::TSSatisfiesExpression(_) | react_compiler_ast::patterns::PatternLike::TSNonNullExpression(_) - | react_compiler_ast::patterns::PatternLike::TSTypeAssertion(_) => {} + | react_compiler_ast::patterns::PatternLike::TSTypeAssertion(_) + | react_compiler_ast::patterns::PatternLike::TypeCastExpression(_) => {} } } diff --git a/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs b/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs index 9d7e34e24d79..fb54d7de4101 100644 --- a/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs +++ b/compiler/crates/react_compiler_lowering/src/find_context_identifiers.rs @@ -234,6 +234,13 @@ fn walk_lval_for_reassignment( convert_opt_loc(&node.base.loc), )?; } + PatternLike::TypeCastExpression(node) => { + record_unsupported_lval( + visitor.env, + "TypeCastExpression", + convert_opt_loc(&node.base.loc), + )?; + } } Ok(()) } diff --git a/compiler/crates/react_compiler_oxc/src/convert_ast.rs b/compiler/crates/react_compiler_oxc/src/convert_ast.rs index 37d56fec6b91..4b1b8a3e9f9c 100644 --- a/compiler/crates/react_compiler_oxc/src/convert_ast.rs +++ b/compiler/crates/react_compiler_oxc/src/convert_ast.rs @@ -2637,7 +2637,8 @@ impl<'a> ConvertCtx<'a> { PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => {} + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => {} } } diff --git a/compiler/crates/react_compiler_oxc/src/convert_ast_reverse.rs b/compiler/crates/react_compiler_oxc/src/convert_ast_reverse.rs index 1a5de6bdb950..9f6ee0fd5da2 100644 --- a/compiler/crates/react_compiler_oxc/src/convert_ast_reverse.rs +++ b/compiler/crates/react_compiler_oxc/src/convert_ast_reverse.rs @@ -1156,7 +1156,8 @@ impl<'a> ReverseCtx<'a> { | PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => self + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => self .builder .binding_pattern_binding_identifier(SPAN, self.atom("__member_pattern__")), } @@ -1285,7 +1286,8 @@ impl<'a> ReverseCtx<'a> { PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => self + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => self .builder .simple_assignment_target_assignment_target_identifier( SPAN, diff --git a/compiler/crates/react_compiler_swc/src/convert_ast_reverse.rs b/compiler/crates/react_compiler_swc/src/convert_ast_reverse.rs index 56529a231df5..843e653b95a5 100644 --- a/compiler/crates/react_compiler_swc/src/convert_ast_reverse.rs +++ b/compiler/crates/react_compiler_swc/src/convert_ast_reverse.rs @@ -1577,6 +1577,9 @@ impl ReverseCtx { PatternLike::TSTypeAssertion(e) => { Pat::Expr(Box::new(self.convert_expression(&e.expression))) } + PatternLike::TypeCastExpression(e) => { + Pat::Expr(Box::new(self.convert_expression(&e.expression))) + } } } @@ -1624,9 +1627,10 @@ impl ReverseCtx { PatternLike::TSAsExpression(_) | PatternLike::TSSatisfiesExpression(_) | PatternLike::TSNonNullExpression(_) - | PatternLike::TSTypeAssertion(_) => AssignTarget::Simple(SimpleAssignTarget::Ident( - self.binding_ident("__unknown__", DUMMY_SP), - )), + | PatternLike::TSTypeAssertion(_) + | PatternLike::TypeCastExpression(_) => AssignTarget::Simple( + SimpleAssignTarget::Ident(self.binding_ident("__unknown__", DUMMY_SP)), + ), } }