From fe16cf8c2ea7a6717f4c7454b6c3c16fa91b22d2 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 26 Jun 2026 13:02:43 -0700 Subject: [PATCH 1/3] Fix ctor/setProp prop-name gaps surfaced by iterator-setter audit (#57330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Auditing every Props .cpp file in the renderer surfaced six cases where a prop key parsed by the 3-arg constructor's `convertRawProp(...)` call had no matching `case` in the same class's `setProp` method. With the iterator-setter path now routing every prop through `setProp` (instead of re-running the parsing constructor), each gap silently drops the prop on the floor when the iterator-setter path is active. Fix each gap: - `BaseScrollViewProps::setProp` — add `RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustKeyboardInsets)`. Parsed in ctor, missing case. - `BaseTextProps::setProp` — add `REBUILD_FIELD_SWITCH_CASE(... dynamicTypeRamp, "dynamicTypeRamp")`. Parsed in ctor, missing case. - `BaseTextProps::setProp` — rename the `baseWritingDirection` switch case key to `writingDirection`. JS sends `writingDirection` (the C++ field is named `baseWritingDirection` internally, but JS / Flow / TS / codegen all use `writingDirection` — verified across `StyleSheetTypes.js`, `StyleSheetTypes.d.ts`, `ReactNativeStyleAttributes.js`, `RCTTextInputViewConfig.js`, and the `Text-itest.js` integration test mounts ``). The setProp case was keyed off the C++ field name and never fired for any real JS prop. `appendTextAttributesProps` still emits `baseWritingDirection` for the C++→JS diff path — that's a separate, out-of-scope inconsistency. - `AccessibilityProps::setProp` — rename the `accessibilityOrder` switch case key to `experimental_accessibilityOrder`. JS sends the prefixed name (verified in `ViewPropTypes.js`, `BaseViewConfig.android.js`, `BaseViewConfig.ios.js`, `ViewProps.kt`'s `ACCESSIBILITY_ORDER = "experimental_accessibilityOrder"` constant, and `HostPlatformViewProps::getDebugProps` which serializes back out as `experimental_accessibilityOrder`). The unprefixed case never fired. - `BaseViewProps::setProp` — add `RAW_SET_PROP_SWITCH_CASE_BASIC(transformOrigin)`. Parsed in ctor, missing case. - `BaseViewProps::setProp` — add `SET_CASCADED_RECTANGLE_CORNERS(borderCurves, "border", "Curve", value)`. The ctor parses the full 13-key cascaded set (`borderCurve`, `borderTopLeftCurve`, … `borderStartStartCurve`) via `CascadedRectangleCornersNames`, but `setProp` had `SET_CASCADED_RECTANGLE_CORNERS` for `borderRadii` and `SET_CASCADED_RECTANGLE_EDGES` for `borderColors`/`borderStyles` — the corresponding `borderCurves` invocation was missing. Same gaps in the `third-party/react-native-macos/.../BaseViewProps.cpp` mirror — applied the same fixes there. Out-of-scope but flagged by the audit (left for follow-ups): - `propsConversions.h`'s `ViewEvents` converter doesn't handle `onGotPointerCapture` / `onLostPointerCapture` (explicit `// TODO` at line 541). `BaseViewProps::setProp` does. Asymmetry runs the other direction — iterator-setter handles these, classic ctor doesn't. - android `HostPlatformViewProps.cpp` defines `VIEW_EVENT_CASE` macro but never invokes it in its switch — dead code (events live on `BaseViewProps::events`). Cleanup candidate. Changelog: [General][Fixed] - Several view, text, scrollview, and accessibility props that the iterator-setter path silently dropped now propagate correctly through `setProp`: `automaticallyAdjustKeyboardInsets`, `dynamicTypeRamp`, `writingDirection`, `experimental_accessibilityOrder`, `transformOrigin`, and the full `borderCurves` cascaded set. Differential Revision: D109584760 --- .../components/scrollview/BaseScrollViewProps.cpp | 1 + .../renderer/components/text/BaseTextProps.cpp | 4 +++- .../renderer/components/view/AccessibilityProps.cpp | 3 ++- .../renderer/components/view/BaseViewProps.cpp | 2 ++ .../components/view/HostPlatformViewProps.cpp | 12 ------------ .../renderer/components/view/propsConversions.h | 13 ++++++++++++- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp index a683cb87f825..93f7cc0a29e1 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp @@ -395,6 +395,7 @@ void BaseScrollViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(centerContent); RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustContentInsets); RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustsScrollIndicatorInsets); + RAW_SET_PROP_SWITCH_CASE_BASIC(automaticallyAdjustKeyboardInsets); RAW_SET_PROP_SWITCH_CASE_BASIC(decelerationRate); RAW_SET_PROP_SWITCH_CASE_BASIC(directionalLockEnabled); RAW_SET_PROP_SWITCH_CASE_BASIC(indicatorStyle); diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp index e2ab6780f96d..bf0019e02815 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp @@ -273,6 +273,8 @@ void BaseTextProps::setProp( textAttributes, maxFontSizeMultiplier, "maxFontSizeMultiplier"); + REBUILD_FIELD_SWITCH_CASE( + defaults, value, textAttributes, dynamicTypeRamp, "dynamicTypeRamp"); REBUILD_FIELD_SWITCH_CASE( defaults, value, textAttributes, letterSpacing, "letterSpacing"); REBUILD_FIELD_SWITCH_CASE( @@ -286,7 +288,7 @@ void BaseTextProps::setProp( value, textAttributes, baseWritingDirection, - "baseWritingDirection"); + "writingDirection"); REBUILD_FIELD_SWITCH_CASE( defaults, value, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp index f94ac81e59f3..c9356759d7a9 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp @@ -267,7 +267,8 @@ void AccessibilityProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(accessible); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityState); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabel); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityOrder); + RAW_SET_PROP_SWITCH_CASE( + accessibilityOrder, "experimental_accessibilityOrder"); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabelledBy); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLiveRegion); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityHint); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index d3bae3163c09..50e57aeb70d9 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -465,6 +465,7 @@ void BaseViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(shadowOpacity); RAW_SET_PROP_SWITCH_CASE_BASIC(shadowRadius); RAW_SET_PROP_SWITCH_CASE_BASIC(transform); + RAW_SET_PROP_SWITCH_CASE_BASIC(transformOrigin); RAW_SET_PROP_SWITCH_CASE_BASIC(backfaceVisibility); RAW_SET_PROP_SWITCH_CASE_BASIC(shouldRasterize); RAW_SET_PROP_SWITCH_CASE_BASIC(zIndex); @@ -522,6 +523,7 @@ void BaseViewProps::setProp( // BorderRadii SET_CASCADED_RECTANGLE_CORNERS(borderRadii, "border", "Radius", value); SET_CASCADED_RECTANGLE_EDGES(borderColors, "border", "Color", value); + SET_CASCADED_RECTANGLE_CORNERS(borderCurves, "border", "Curve", value); SET_CASCADED_RECTANGLE_EDGES(borderStyles, "border", "Style", value); } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index 304cfa8c27da..4a9bf3c78b39 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -143,18 +143,6 @@ HostPlatformViewProps::HostPlatformViewProps( sourceProps.nextFocusUp, {})) {} -#define VIEW_EVENT_CASE(eventType) \ - case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \ - const auto offset = ViewEvents::Offset::eventType; \ - ViewEvents defaultViewEvents{}; \ - bool res = defaultViewEvents[offset]; \ - if (value.hasValue()) { \ - fromRawValue(context, value, res); \ - } \ - events[offset] = res; \ - return; \ - } - void HostPlatformViewProps::setProp( const PropsParserContext& context, RawPropsPropNameHash hash, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h index 54602ebaf2f1..5b19dfc2c077 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/propsConversions.h @@ -538,7 +538,18 @@ static inline ViewEvents convertRawProp( "onPointerUpCapture", sourceValue[Offset::PointerUpCapture], defaultValue[Offset::PointerUpCapture]); - // TODO: gotPointerCapture & lostPointerCapture + result[Offset::GotPointerCapture] = convertRawProp( + context, + rawProps, + "onGotPointerCapture", + sourceValue[Offset::GotPointerCapture], + defaultValue[Offset::GotPointerCapture]); + result[Offset::LostPointerCapture] = convertRawProp( + context, + rawProps, + "onLostPointerCapture", + sourceValue[Offset::LostPointerCapture], + defaultValue[Offset::LostPointerCapture]); // PanResponder callbacks result[Offset::MoveShouldSetResponder] = convertRawProp( From c4b922442e23daea271819648281152eb9f43406 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 26 Jun 2026 13:02:43 -0700 Subject: [PATCH 2/3] Route `enableCppPropsIteratorSetter` through a copy ctor + `RawProps::forEachItem` (#57328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Today the iterator-setter path in `ConcreteComponentDescriptor::cloneProps` runs three sequential walks over the input — `RawProps::parse(parser)` (builds `keyIndexToValueIndex_` for `convertRawProp`), `static_cast(rawProps)` (materializes a `folly::dynamic` via `jsi::dynamicFromValue` in JSI mode), and then `dynamic.items()` to dispatch `setProp`. Only the third is actually used: `convertRawProp` is never called on the iterator-setter branch, and the `folly::dynamic` materialization exists only as iteration scaffolding. Restructure so the runtime flag picks one of two construction paths up front: - **Iterator-setter** — copy-construct from `sourceProps` via the (re-enabled) `Props` copy ctor, then walk `rawProps` in-place via the new `RawProps::forEachItem` helper and route each entry through `setProp`. `parse()` is skipped entirely; the `folly::dynamic` materialization is skipped in `Mode::JSI`. - **Classic** — unchanged: `parse()` + 3-arg `convertRawProp`-driven ctor. `forEachItem` switches on `RawProps::Mode`: - `Mode::JSI` — walks `value_.asObject(*runtime_).getPropertyNames(...)` and constructs `RawValue` from each `jsi::Value` directly, no `folly::dynamic` in between. - `Mode::Dynamic` — iterates `dynamic_.items()` (same as today). - `Mode::Empty` — no-op. A new `HasIteratorSetterCtor` concept (`std::copy_constructible`) documents the contract and feeds a `static_assert` in `cloneProps`, so a future Props type that deletes its copy ctor fails at compile time rather than silently diverging at runtime between the two flag states. The `RN_SERIALIZABLE_STATE` Props 2.0 accumulation branch keeps its existing dynamic-iteration shape — when `fallbackToDynamicRawPropsAccumulation` is true, `initializeDynamicProps` has already merged the source's rawProps with the input onto `shadowNodeProps->rawProps`, so we iterate that merged dynamic rather than the raw input. The per-field `flag ? sourceProps.X : convertRawProp(...)` ternaries across every Props .cpp file become dead in the flag-on path (the copy ctor handles those fields) but are still functional in the flag-off path. They get removed in a follow-up cleanup; this diff is structurally non-breaking on either flag state. Changelog: [Internal] Differential Revision: D109568749 --- .../core/ConcreteComponentDescriptor.h | 44 ++++++++----- .../react/renderer/core/ConcreteShadowNode.h | 19 ++++++ .../ReactCommon/react/renderer/core/Props.cpp | 8 +-- .../ReactCommon/react/renderer/core/Props.h | 64 ++++++++++++++++++- .../react/renderer/core/RawProps.h | 39 +++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 16 ++++- .../api-snapshots/ReactAndroidNewarchCxx.api | 16 ++++- .../api-snapshots/ReactAndroidReleaseCxx.api | 16 ++++- .../api-snapshots/ReactAppleDebugCxx.api | 16 ++++- .../api-snapshots/ReactAppleNewarchCxx.api | 16 ++++- .../api-snapshots/ReactAppleReleaseCxx.api | 16 ++++- .../api-snapshots/ReactCommonDebugCxx.api | 16 ++++- .../api-snapshots/ReactCommonNewarchCxx.api | 16 ++++- .../api-snapshots/ReactCommonReleaseCxx.api | 16 ++++- 14 files changed, 286 insertions(+), 32 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index 495702a08b8d..2945e433c383 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -112,9 +112,30 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { ShadowNodeT::filterRawProps(rawProps); } - rawProps.parse(rawPropsParser_); + // Two construction paths: + // - Iterator-setter (only available when `ConcreteProps` satisfies + // `HasIteratorSetterCtor` AND the runtime flag is on): copy-construct + // from sourceProps, then walk rawProps in-place via `forEachItem` and + // route each entry through `setProp`. Skips both + // `RawProps::parse(parser)` and the `folly::dynamic` materialization + // that the legacy path needed. + // - Classic (the fallback for any `ConcreteProps` that doesn't opt in, + // and the only path when the flag is off): parse + per-field + // `convertRawProp` via the 3-arg ctor. + constexpr bool kSupportsIteratorSetter = HasIteratorSetterCtor; + const bool useIteratorSetter = kSupportsIteratorSetter && ReactNativeFeatureFlags::enableCppPropsIteratorSetter(); + + std::shared_ptr shadowNodeProps; + if constexpr (kSupportsIteratorSetter) { + if (useIteratorSetter) { + shadowNodeProps = ShadowNodeT::Props(props); + } + } + if (!useIteratorSetter) { + rawProps.parse(rawPropsParser_); + shadowNodeProps = ShadowNodeT::Props(context, rawProps, props); + } - auto shadowNodeProps = ShadowNodeT::Props(context, rawProps, props); #ifdef RN_SERIALIZABLE_STATE bool fallbackToDynamicRawPropsAccumulation = true; if (ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid() && @@ -134,19 +155,12 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { ShadowNodeT::initializeDynamicProps(shadowNodeProps, rawProps, props); } #endif - // Use the new-style iterator - // Note that we just check if `Props` has this flag set, no matter - // the type of ShadowNode; it acts as the single global flag. - if (ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) { -#ifdef RN_SERIALIZABLE_STATE - const auto &dynamic = - fallbackToDynamicRawPropsAccumulation ? shadowNodeProps->rawProps : static_cast(rawProps); -#else - const auto &dynamic = static_cast(rawProps); -#endif - for (const auto &pair : dynamic.items()) { - const auto &name = pair.first.getString(); - shadowNodeProps->setProp(context, RAW_PROPS_KEY_HASH(name), name.c_str(), RawValue(pair.second)); + + if constexpr (kSupportsIteratorSetter) { + if (useIteratorSetter) { + rawProps.forEachItem([&](std::string_view name, const RawValue &value) { + shadowNodeProps->setProp(context, RAW_PROPS_KEY_HASH(name), name.data(), value); + }); } } return shadowNodeProps; diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h index cbddd0d8b446..ac069108548c 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteShadowNode.h @@ -71,6 +71,13 @@ class ConcreteShadowNode : public BaseShadowNodeT { return BaseShadowNodeT::BaseTraits(); } + /* + * Classic / parse path: construct `PropsT` by parsing `rawProps` field-by- + * field via the 3-arg `(context, sourceProps, rawProps)` constructor. + * `ConcreteComponentDescriptor::cloneProps` calls this when the + * iterator-setter path is disabled (per-class via the + * `HasIteratorSetterCtor` concept, or globally via the runtime flag). + */ static UnsharedConcreteProps Props(const PropsParserContext &context, const RawProps &rawProps, const Props::Shared &baseProps = nullptr) { @@ -78,6 +85,18 @@ class ConcreteShadowNode : public BaseShadowNodeT { context, baseProps ? static_cast(*baseProps) : *defaultSharedProps(), rawProps); } + /* + * Iterator-setter path: copy-construct `PropsT` from `baseProps` only. + * `ConcreteComponentDescriptor::cloneProps` then walks `rawProps` via + * `RawProps::forEachItem` and overwrites individual fields through + * `PropsT::setProp`. Available when `PropsT` satisfies + * `HasIteratorSetterCtor`. + */ + static UnsharedConcreteProps Props(const Props::Shared &baseProps) + { + return std::make_shared(baseProps ? static_cast(*baseProps) : *defaultSharedProps()); + } + #ifdef RN_SERIALIZABLE_STATE static void initializeDynamicProps( UnsharedConcreteProps props, diff --git a/packages/react-native/ReactCommon/react/renderer/core/Props.cpp b/packages/react-native/ReactCommon/react/renderer/core/Props.cpp index aca087d296e4..6c668c044184 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/Props.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/Props.cpp @@ -29,13 +29,7 @@ Props::Props( rawProps, "nativeID", sourceProps.nativeId, - {})) { -#ifdef RN_SERIALIZABLE_STATE - if (!ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid()) { - initializeDynamicProps(sourceProps, rawProps, filterObjectKeys); - } -#endif -} + {})) {} void Props::setProp( const PropsParserContext& context, diff --git a/packages/react-native/ReactCommon/react/renderer/core/Props.h b/packages/react-native/ReactCommon/react/renderer/core/Props.h index ad3e264d31e2..a197824f1bff 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/Props.h +++ b/packages/react-native/ReactCommon/react/renderer/core/Props.h @@ -41,7 +41,7 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { virtual ~Props() = default; #endif - Props(const Props &other) = delete; + Props(const Props &other) = default; Props &operator=(const Props &other) = delete; /** @@ -84,4 +84,66 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { #endif }; +namespace detail { + +/* + * Extracts the class type from a pointer-to-member-function expression. + * Used in unevaluated context only. + */ +template +auto memberFunctionClass(T C::*) -> C; + +} // namespace detail + +/* + * Internal: `T` declares its OWN `setProp` (not just inherits one from a + * base class). `&T::setProp` resolves to a pointer-to-member-function whose + * class part is the level where `setProp` was actually declared — if `T` + * inherits without overriding, that class is some base, not `T`. + * + * Distinguishing own-declaration from inherited-declaration matters because + * `setProp` is non-virtual. A subclass that adds fields but forgets to + * override `setProp` would silently inherit its parent's switch — and the new + * fields would never be reached by the iterator-setter dispatch. + */ +template +concept DeclaresOwnSetProp = std::is_same_v; + +/* + * Internal: `T` exposes a `setProp(ctx, hash, name, value) -> void` callable + * with the canonical Props signature, declared on `T` itself. + */ +template +concept HasSetProp = DeclaresOwnSetProp && + requires(T &t, const PropsParserContext &ctx, RawPropsPropNameHash hash, const char *name, const RawValue &value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; + }; + +/* + * Marks a Props type as supporting the iterator-setter construction path used + * by `ConcreteComponentDescriptor::cloneProps` when + * `enableCppPropsIteratorSetter` is on. The contract is: + * + * 1. The type is copy-constructible from a source Props (so `cloneProps` + * can build the new Props by copy and then overwrite individual fields). + * 2. The type descends from `Props`, anchoring the `setProp` chain in the + * `Props::setProp` base case. + * 3. The type declares its OWN `setProp` with the canonical signature — + * not inherited — so the iterator dispatch reaches every field that the + * type adds beyond its base. + * + * `setProp` is non-virtual; subclasses chain explicitly via + * `Parent::setProp(...)`. The chain integrity beyond `T` is enforced by the + * compiler at each level's `setProp` body — if a subclass calls + * `Parent::setProp(...)` and `Parent` does not define one, the build fails + * at that call site. This concept guards the entry point (`T` itself) and + * relies on those per-level calls to keep the chain whole. + * + * When the concept is NOT satisfied for some `ConcreteProps`, + * `cloneProps` falls through to the classic per-field `convertRawProp` path + * for that component regardless of the runtime flag. + */ +template +concept HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/RawProps.h b/packages/react-native/ReactCommon/react/renderer/core/RawProps.h index e1ffd6265d35..85a89c2f3ba3 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/RawProps.h +++ b/packages/react-native/ReactCommon/react/renderer/core/RawProps.h @@ -92,6 +92,45 @@ class RawProps final { */ const RawValue *at(const char *name) const noexcept; + /* + * Iterates the underlying source object and invokes `fn(name, value)` for + * each entry, in source order. Skips parsing — does NOT require a prior + * `parse(parser)` call. For `Mode::JSI` this walks the JSI object in-place + * (no `folly::dynamic` materialization). For `Mode::Dynamic` it walks + * `dynamic_.items()`. For `Mode::Empty` it is a no-op. + * + * The callback signature is `void(std::string_view name, const RawValue &value)`. + * The view points into storage owned by `forEachItem` for the duration of + * the call and is null-terminated (i.e. `name.data()` is a valid C string). + */ + template + void forEachItem(Fn fn) const + { + switch (mode_) { + case Mode::Empty: + return; + case Mode::JSI: { + auto object = value_.asObject(*runtime_); + auto names = object.getPropertyNames(*runtime_); + auto count = names.size(*runtime_); + for (size_t i = 0; i < count; ++i) { + auto name = names.getValueAtIndex(*runtime_, i).asString(*runtime_); + auto propValue = object.getProperty(*runtime_, name); + auto nameUtf8 = name.utf8(*runtime_); + fn(std::string_view{nameUtf8}, RawValue{*runtime_, std::move(propValue)}); + } + return; + } + case Mode::Dynamic: + for (const auto &pair : dynamic_.items()) { + fn(std::string_view{pair.first.getString()}, RawValue{pair.second}); + } + return; + default: + return; + } + } + private: friend class RawPropsParser; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 4a5ea93ebca9..8935a39504bf 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -795,6 +795,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -4126,7 +4135,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public folly::dynamic rawProps; @@ -4195,6 +4204,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -8253,6 +8264,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public static void initializeDynamicProps(facebook::react::ConcreteShadowNode::UnsharedConcreteProps props, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); @@ -9937,6 +9949,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 6e821b751f6d..5ad56f03c23f 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -794,6 +794,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -3980,7 +3989,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public folly::dynamic rawProps; @@ -4039,6 +4048,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -8017,6 +8028,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public static void initializeDynamicProps(facebook::react::ConcreteShadowNode::UnsharedConcreteProps props, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); @@ -9563,6 +9575,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 77a90677d4cf..b9668e5a935b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -795,6 +795,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -4123,7 +4132,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public folly::dynamic rawProps; @@ -4192,6 +4201,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -8244,6 +8255,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public static void initializeDynamicProps(facebook::react::ConcreteShadowNode::UnsharedConcreteProps props, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); @@ -9790,6 +9802,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index cb3c73c37a16..303e480ba934 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -3651,6 +3651,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -6313,7 +6322,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public std::string nativeId; @@ -6412,6 +6421,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -10234,6 +10245,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public using ConcreteEventEmitter = EventEmitterT; @@ -11835,6 +11847,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 87d6ca7aa0d8..bbd0339c5726 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3638,6 +3638,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -6195,7 +6204,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public std::string nativeId; @@ -6284,6 +6293,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -10050,6 +10061,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public using ConcreteEventEmitter = EventEmitterT; @@ -11523,6 +11535,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index cff79303f844..480745e51702 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -3651,6 +3651,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -6310,7 +6319,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public std::string nativeId; @@ -6409,6 +6418,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -10225,6 +10236,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public using ConcreteEventEmitter = EventEmitterT; @@ -11698,6 +11710,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 87aaa9ae2891..c0635a575d75 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -404,6 +404,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -2731,7 +2740,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public std::string nativeId; @@ -2777,6 +2786,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -6343,6 +6354,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public using ConcreteEventEmitter = EventEmitterT; @@ -6997,6 +7009,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index ed97d3fdcb84..dfa75b7376c4 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -403,6 +403,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -2625,7 +2634,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public std::string nativeId; @@ -2661,6 +2670,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -6171,6 +6182,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public using ConcreteEventEmitter = EventEmitterT; @@ -6825,6 +6837,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 9a6ba873336c..af1f1ca7428a 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -404,6 +404,15 @@ concept facebook::react::CSSSyntaxVisitorReturn = std::is_default_constructible_ template concept facebook::react::CSSValidCompoundDataType = facebook::react::detail::is_variant_of_data_types::value; template +concept facebook::react::DeclaresOwnSetProp = std::is_same_v; +template +concept facebook::react::HasIteratorSetterCtor = std::copy_constructible && std::derived_from && HasSetProp; +template +concept facebook::react::HasSetProp = DeclaresOwnSetProp && +requires(T& t, const facebook::react::PropsParserContext& ctx, facebook::react::RawPropsPropNameHash hash, const char* name, const facebook::react::RawValue& value) { + { t.setProp(ctx, hash, name, value) } -> std::same_as; +}; +template concept facebook::react::Hashable = !std::is_same_v&& (requires(T a) { { std::hash{}(a) } -> std::convertible_to; }); @@ -2728,7 +2737,7 @@ class facebook::react::PreparedTextCacheKey { class facebook::react::Props : public virtual facebook::react::Sealable, public virtual facebook::react::DebugStringConvertible { public Props() = default; - public Props(const facebook::react::Props& other) = delete; + public Props(const facebook::react::Props& other) = default; public Props(const facebook::react::PropsParserContext& context, const facebook::react::Props& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr); public facebook::react::Props& operator=(const facebook::react::Props& other) = delete; public std::string nativeId; @@ -2774,6 +2783,8 @@ class facebook::react::RawProps { public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; public operator folly::dynamic() const; public void parse(const facebook::react::RawPropsParser& parser) noexcept; + template + public void forEachItem(Fn fn) const; } enum facebook::react::RawProps::Mode { @@ -6334,6 +6345,7 @@ class facebook::react::ConcreteShadowNode : public BaseShadowNodeT { public static facebook::react::ComponentHandle Handle(); public static facebook::react::ComponentName Name(); public static facebook::react::ConcreteShadowNode::ConcreteStateData initialStateData(const facebook::react::Props::Shared&, const facebook::react::ShadowNodeFamily::Shared&, const facebook::react::ComponentDescriptor&); + public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::Props::Shared& baseProps); public static facebook::react::ConcreteShadowNode::UnsharedConcreteProps Props(const facebook::react::PropsParserContext& context, const facebook::react::RawProps& rawProps, const facebook::react::Props::Shared& baseProps = nullptr); public static facebook::react::ShadowNodeTraits BaseTraits(); public using ConcreteEventEmitter = EventEmitterT; @@ -6988,6 +7000,8 @@ template std::optional facebook::react::detail::parseLegacyHslFunction(facebook::react::CSSValueParser& parser); template std::optional facebook::react::detail::parseModernHslFunction(facebook::react::CSSValueParser& parser); +template +C facebook::react::detail::memberFunctionClass(T C::*); template constexpr std::optional facebook::react::detail::normalizeComponent(const std::variant& component, float baseValue); From 27fa8d9fd3f284755df45a11fff13a2cba302b8f Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 26 Jun 2026 13:02:43 -0700 Subject: [PATCH 3/3] Skip `RawPropsParser::prepare()` when the iterator-setter path is active (#57329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: `RawPropsParser::prepare()` runs in `ConcreteComponentDescriptor`'s constructor and builds the parser's `keys_` vector + `nameToIndex_` length-bucketed map by walking every `convertRawProp` call in a probe construction of `ConcreteProps`. The cost is O(n²) in the number of props (the comment in `RawPropsParser::at` notes 4950 lookups for a 100-prop class) and is paid once per component class at app startup. Those data structures are consumed **only** by `RawProps::at()`, which is reached exclusively through the classic per-field `convertRawProp` path. The iterator-setter path skips `parse()` entirely (see the prior diff in the stack), so the prepared parser is dead weight when both: 1. `HasIteratorSetterCtor` is satisfied (i.e. the type opts into the iterator-setter path), AND 2. `enableCppPropsIteratorSetter()` is on at runtime. Guard the call accordingly. Classes that don't satisfy the concept always run `prepare()` (they can only use the classic path); when the runtime flag is off, all classes run it (since they all fall back to classic). Changelog: [Internal] Reviewed By: christophpurrer, lenaic Differential Revision: D109569571 --- .../renderer/core/ConcreteComponentDescriptor.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index 2945e433c383..5c5773028d31 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -48,7 +48,18 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { RawPropsParser &&rawPropsParser = {}) : ComponentDescriptor(parameters, std::move(rawPropsParser)) { - rawPropsParser_.prepare(); + // The parser's `keys_` / `nameToIndex_` are only consumed by + // `RawProps::at()`, which is reached exclusively through the classic + // per-field `convertRawProp` path. When `ConcreteProps` opts into the + // iterator-setter path and the runtime flag is on, `parse()` is never + // called, so the O(n²) preparation here is wasted. Skip it. + if constexpr (HasIteratorSetterCtor) { + if (!ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) { + rawPropsParser_.prepare(); + } + } else { + rawPropsParser_.prepare(); + } } ComponentHandle getComponentHandle() const override