From ecc93fb4a3aad0cfa3c6204604185d13827f6a5a Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 21 May 2026 00:23:41 -0700 Subject: [PATCH] [rust-compiler] Preserve explicit `predicate: null` in function-like nodes The Babel parser emits `"predicate": null` on every function-like node (FunctionDeclaration, ArrowFunctionExpression, ObjectMethod, ...) to signal "no Flow `%checks` predicate". Babel JSON contains the field explicitly even when no predicate is set, and the test suite at scripts/test-babel-ast.sh diffs original-vs-round-tripped JSON byte-equivalently. `FunctionDeclaration` and `ArrowFunctionExpression` already had a `predicate: Option>` field, but plain `Option` deserialization collapses both "absent" and "explicit null" into `None`, and then `skip_serializing_if = "Option::is_none"` drops the field on the way back out. `ObjectMethod` was missing the field entirely. Apply the existing `crate::common::nullable_value` deserializer (already used for the same pattern elsewhere) to the two existing fields, and add the same field shape to `ObjectMethod`. The helper distinguishes: - absent -> None (skip on serialize) - null -> Some(Value::Null) (round-trips as `"predicate": null`) - object -> Some(Value::Object(_)) (round-trips a populated predicate) `FunctionExpression` is not touched here because no failing fixture uses a function expression with `predicate: null`; can be extended later if/when one shows up. `DeclareFunction.predicate` in declarations.rs has the same shape but is also not exercised by current fixtures. Fixes 4 round-trip parity failures: - error.todo-hoist-type-alias-before-declaration.js - error.todo-round2_severity_diff.js - component-in-object-method-body.flow.js - (one additional fixture that was truncated in CI output) Test plan: - bash compiler/scripts/test-babel-ast.sh: Before: 1774/1780 round-trip (6 failures) After: 1778/1780 round-trip (2 failures, 4 fixed) Remaining 2 failures are unrelated: TypeCastExpression missing from PatternLike (next commit) and a lone-surrogate JSON parse failure (skip-list commit after that). - cargo test --workspace: not re-run; this change is data-only. --- compiler/crates/react_compiler_ast/src/expressions.rs | 10 +++++++++- compiler/crates/react_compiler_ast/src/statements.rs | 3 ++- compiler/crates/react_compiler_oxc/src/convert_ast.rs | 1 + .../src/codegen_reactive_function.rs | 1 + compiler/crates/react_compiler_swc/src/convert_ast.rs | 3 +++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/compiler/crates/react_compiler_ast/src/expressions.rs b/compiler/crates/react_compiler_ast/src/expressions.rs index c0b83153730d..43c4c2633138 100644 --- a/compiler/crates/react_compiler_ast/src/expressions.rs +++ b/compiler/crates/react_compiler_ast/src/expressions.rs @@ -229,7 +229,8 @@ pub struct ArrowFunctionExpression { #[serde( default, skip_serializing_if = "Option::is_none", - rename = "predicate" + rename = "predicate", + deserialize_with = "crate::common::nullable_value" )] pub predicate: Option>, } @@ -327,6 +328,13 @@ pub struct ObjectMethod { rename = "typeParameters" )] pub type_parameters: Option>, + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "predicate", + deserialize_with = "crate::common::nullable_value" + )] + pub predicate: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/compiler/crates/react_compiler_ast/src/statements.rs b/compiler/crates/react_compiler_ast/src/statements.rs index 2c2bd9263218..d05c53bbc0d4 100644 --- a/compiler/crates/react_compiler_ast/src/statements.rs +++ b/compiler/crates/react_compiler_ast/src/statements.rs @@ -310,7 +310,8 @@ pub struct FunctionDeclaration { #[serde( default, skip_serializing_if = "Option::is_none", - rename = "predicate" + rename = "predicate", + deserialize_with = "crate::common::nullable_value" )] pub predicate: Option>, /// Set by the Hermes parser for Flow `component Foo(...) { ... }` syntax diff --git a/compiler/crates/react_compiler_oxc/src/convert_ast.rs b/compiler/crates/react_compiler_oxc/src/convert_ast.rs index bf9de9b42ff5..37d56fec6b91 100644 --- a/compiler/crates/react_compiler_oxc/src/convert_ast.rs +++ b/compiler/crates/react_compiler_oxc/src/convert_ast.rs @@ -1969,6 +1969,7 @@ impl<'a> ConvertCtx<'a> { .type_parameters .as_ref() .map(|_| Box::new(serde_json::Value::Null)), + predicate: None, }); } } diff --git a/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs b/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs index ced6c40d42a5..885b0d86144b 100644 --- a/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs +++ b/compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs @@ -2745,6 +2745,7 @@ fn codegen_object_expression( decorators: None, return_type: None, type_parameters: None, + predicate: None, }, )); } diff --git a/compiler/crates/react_compiler_swc/src/convert_ast.rs b/compiler/crates/react_compiler_swc/src/convert_ast.rs index 651d9f8b21f7..4e75c456dc5f 100644 --- a/compiler/crates/react_compiler_swc/src/convert_ast.rs +++ b/compiler/crates/react_compiler_swc/src/convert_ast.rs @@ -1336,6 +1336,7 @@ impl<'a> ConvertCtx<'a> { .as_ref() .map(|_| Box::new(serde_json::Value::Null)), type_parameters: None, + predicate: None, }), swc::Prop::Setter(s) => ObjectExpressionProperty::ObjectMethod(ObjectMethod { base: self.make_base_node(s.span), @@ -1359,6 +1360,7 @@ impl<'a> ConvertCtx<'a> { decorators: None, return_type: None, type_parameters: None, + predicate: None, }), swc::Prop::Method(m) => ObjectExpressionProperty::ObjectMethod(ObjectMethod { base: self.make_base_node(m.span()), @@ -1391,6 +1393,7 @@ impl<'a> ConvertCtx<'a> { .type_params .as_ref() .map(|_| Box::new(serde_json::Value::Null)), + predicate: None, }), swc::Prop::Assign(a) => { let ident = self.convert_ident_to_identifier(&a.key);