From 6922f83597938c4597cb067d548c5f36722556c9 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 8 Feb 2024 16:22:38 -0500 Subject: [PATCH] Wire up debugInfo from elements/arrays/lazy/thenables to fibers Merge any debug info we got from intermediate objects until we create or update a fiber that can hold it. --- .../react-client/src/ReactFlightClient.js | 1 - .../react-reconciler/src/ReactChildFiber.js | 206 ++++++++++++++++-- .../src/ReactFiberBeginWork.js | 4 + .../react-server/src/ReactFlightServer.js | 1 - packages/shared/ReactTypes.js | 8 +- 5 files changed, 198 insertions(+), 22 deletions(-) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index b88f10665ec7e..10452c45b924e 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -12,7 +12,6 @@ import type { ReactDebugInfo, ReactComponentInfo, ReactAsyncInfo, - ReactConsoleInfo, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js index 5f6013d14b49f..6c273faed4b0b 100644 --- a/packages/react-reconciler/src/ReactChildFiber.js +++ b/packages/react-reconciler/src/ReactChildFiber.js @@ -8,7 +8,12 @@ */ import type {ReactElement} from 'shared/ReactElementType'; -import type {ReactPortal, Thenable, ReactContext} from 'shared/ReactTypes'; +import type { + ReactPortal, + Thenable, + ReactContext, + ReactDebugInfo, +} from 'shared/ReactTypes'; import type {Fiber} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane'; import type {ThenableState} from './ReactFiberThenable'; @@ -50,6 +55,25 @@ import {readContextDuringReconcilation} from './ReactFiberNewContext'; let thenableState: ThenableState | null = null; let thenableIndexCounter: number = 0; +function mergeDebugInfo( + outer: ReactDebugInfo | null, + inner: ReactDebugInfo | null | void, +): ReactDebugInfo | null { + if (!__DEV__) { + return null; + } + if (inner == null) { + return outer; + } else if (outer === null) { + return inner; + } else { + // If we have two debugInfo, we need to create a new one. This makes the array no longer + // live so we'll miss any future updates if we received more so ideally we should always + // do this after both have fully resolved/unsuspended. + return outer.concat(inner); + } +} + let didWarnAboutMaps; let didWarnAboutGenerators; let didWarnAboutStringRefs; @@ -387,16 +411,23 @@ function createChildReconciler( current: Fiber | null, textContent: string, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ) { if (current === null || current.tag !== HostText) { // Insert const created = createFiberFromText(textContent, returnFiber.mode, lanes); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { // Update const existing = useFiber(current, textContent); existing.return = returnFiber; + if (__DEV__) { + existing._debugInfo = debugInfo; + } return existing; } } @@ -406,6 +437,7 @@ function createChildReconciler( current: Fiber | null, element: ReactElement, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { const elementType = element.type; if (elementType === REACT_FRAGMENT_TYPE) { @@ -415,6 +447,7 @@ function createChildReconciler( element.props.children, lanes, element.key, + debugInfo, ); } if (current !== null) { @@ -439,6 +472,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; + existing._debugInfo = debugInfo; } return existing; } @@ -447,6 +481,9 @@ function createChildReconciler( const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber, current, element); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } @@ -455,6 +492,7 @@ function createChildReconciler( current: Fiber | null, portal: ReactPortal, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { if ( current === null || @@ -465,11 +503,17 @@ function createChildReconciler( // Insert const created = createFiberFromPortal(portal, returnFiber.mode, lanes); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { // Update const existing = useFiber(current, portal.children || []); existing.return = returnFiber; + if (__DEV__) { + existing._debugInfo = debugInfo; + } return existing; } } @@ -480,6 +524,7 @@ function createChildReconciler( fragment: Iterable, lanes: Lanes, key: null | string, + debugInfo: null | ReactDebugInfo, ): Fiber { if (current === null || current.tag !== Fragment) { // Insert @@ -490,11 +535,17 @@ function createChildReconciler( key, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { // Update const existing = useFiber(current, fragment); existing.return = returnFiber; + if (__DEV__) { + existing._debugInfo = debugInfo; + } return existing; } } @@ -503,6 +554,7 @@ function createChildReconciler( returnFiber: Fiber, newChild: any, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || @@ -517,6 +569,9 @@ function createChildReconciler( lanes, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } @@ -530,6 +585,9 @@ function createChildReconciler( ); created.ref = coerceRef(returnFiber, null, newChild); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo); + } return created; } case REACT_PORTAL_TYPE: { @@ -539,12 +597,20 @@ function createChildReconciler( lanes, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } case REACT_LAZY_TYPE: { const payload = newChild._payload; const init = newChild._init; - return createChild(returnFiber, init(payload), lanes); + return createChild( + returnFiber, + init(payload), + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), // call merge after init + ); } } @@ -556,6 +622,9 @@ function createChildReconciler( null, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo); + } return created; } @@ -564,7 +633,12 @@ function createChildReconciler( // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { const thenable: Thenable = (newChild: any); - return createChild(returnFiber, unwrapThenable(thenable), lanes); + return createChild( + returnFiber, + unwrapThenable(thenable), + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } if (newChild.$$typeof === REACT_CONTEXT_TYPE) { @@ -573,6 +647,7 @@ function createChildReconciler( returnFiber, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -593,6 +668,7 @@ function createChildReconciler( oldFiber: Fiber | null, newChild: any, lanes: Lanes, + debugInfo: null | ReactDebugInfo, ): Fiber | null { // Update the fiber if the keys match, otherwise return null. const key = oldFiber !== null ? oldFiber.key : null; @@ -607,21 +683,39 @@ function createChildReconciler( if (key !== null) { return null; } - return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes); + return updateTextNode( + returnFiber, + oldFiber, + '' + newChild, + lanes, + debugInfo, + ); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { if (newChild.key === key) { - return updateElement(returnFiber, oldFiber, newChild, lanes); + return updateElement( + returnFiber, + oldFiber, + newChild, + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } else { return null; } } case REACT_PORTAL_TYPE: { if (newChild.key === key) { - return updatePortal(returnFiber, oldFiber, newChild, lanes); + return updatePortal( + returnFiber, + oldFiber, + newChild, + lanes, + debugInfo, + ); } else { return null; } @@ -629,7 +723,13 @@ function createChildReconciler( case REACT_LAZY_TYPE: { const payload = newChild._payload; const init = newChild._init; - return updateSlot(returnFiber, oldFiber, init(payload), lanes); + return updateSlot( + returnFiber, + oldFiber, + init(payload), + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } } @@ -638,7 +738,14 @@ function createChildReconciler( return null; } - return updateFragment(returnFiber, oldFiber, newChild, lanes, null); + return updateFragment( + returnFiber, + oldFiber, + newChild, + lanes, + null, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } // Usable node types @@ -651,6 +758,7 @@ function createChildReconciler( oldFiber, unwrapThenable(thenable), lanes, + debugInfo, ); } @@ -661,6 +769,7 @@ function createChildReconciler( oldFiber, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -682,6 +791,7 @@ function createChildReconciler( newIdx: number, newChild: any, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || @@ -690,7 +800,13 @@ function createChildReconciler( // Text nodes don't have keys, so we neither have to check the old nor // new node for the key. If both are text nodes, they match. const matchedFiber = existingChildren.get(newIdx) || null; - return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes); + return updateTextNode( + returnFiber, + matchedFiber, + '' + newChild, + lanes, + debugInfo, + ); } if (typeof newChild === 'object' && newChild !== null) { @@ -700,14 +816,26 @@ function createChildReconciler( existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; - return updateElement(returnFiber, matchedFiber, newChild, lanes); + return updateElement( + returnFiber, + matchedFiber, + newChild, + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } case REACT_PORTAL_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; - return updatePortal(returnFiber, matchedFiber, newChild, lanes); + return updatePortal( + returnFiber, + matchedFiber, + newChild, + lanes, + debugInfo, + ); } case REACT_LAZY_TYPE: const payload = newChild._payload; @@ -718,12 +846,20 @@ function createChildReconciler( newIdx, init(payload), lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } if (isArray(newChild) || getIteratorFn(newChild)) { const matchedFiber = existingChildren.get(newIdx) || null; - return updateFragment(returnFiber, matchedFiber, newChild, lanes, null); + return updateFragment( + returnFiber, + matchedFiber, + newChild, + lanes, + null, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } // Usable node types @@ -737,6 +873,7 @@ function createChildReconciler( newIdx, unwrapThenable(thenable), lanes, + debugInfo, ); } @@ -748,6 +885,7 @@ function createChildReconciler( newIdx, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -818,6 +956,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildren: Array, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { // This algorithm can't optimize by searching from both ends since we // don't have backpointers on fibers. I'm trying to see how far we can get @@ -866,6 +1005,7 @@ function createChildReconciler( oldFiber, newChildren[newIdx], lanes, + debugInfo, ); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's @@ -913,7 +1053,12 @@ function createChildReconciler( // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { - const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); + const newFiber = createChild( + returnFiber, + newChildren[newIdx], + lanes, + debugInfo, + ); if (newFiber === null) { continue; } @@ -944,6 +1089,7 @@ function createChildReconciler( newIdx, newChildren[newIdx], lanes, + debugInfo, ); if (newFiber !== null) { if (shouldTrackSideEffects) { @@ -985,6 +1131,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildrenIterable: Iterable, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { // This is the same implementation as reconcileChildrenArray(), // but using the iterator instead. @@ -1068,7 +1215,13 @@ function createChildReconciler( } else { nextOldFiber = oldFiber.sibling; } - const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes); + const newFiber = updateSlot( + returnFiber, + oldFiber, + step.value, + lanes, + debugInfo, + ); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need @@ -1115,7 +1268,7 @@ function createChildReconciler( // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; !step.done; newIdx++, step = newChildren.next()) { - const newFiber = createChild(returnFiber, step.value, lanes); + const newFiber = createChild(returnFiber, step.value, lanes, debugInfo); if (newFiber === null) { continue; } @@ -1146,6 +1299,7 @@ function createChildReconciler( newIdx, step.value, lanes, + debugInfo, ); if (newFiber !== null) { if (shouldTrackSideEffects) { @@ -1211,6 +1365,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, element: ReactElement, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { const key = element.key; let child = currentFirstChild; @@ -1226,6 +1381,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; + existing._debugInfo = debugInfo; } return existing; } @@ -1251,6 +1407,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; + existing._debugInfo = debugInfo; } return existing; } @@ -1272,11 +1429,17 @@ function createChildReconciler( element.key, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } } @@ -1286,6 +1449,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, portal: ReactPortal, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { const key = portal.key; let child = currentFirstChild; @@ -1325,6 +1489,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { // This function is not recursive. // If the top level item is an array, we treat it as a set of children, @@ -1354,6 +1519,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ), ); case REACT_PORTAL_TYPE: @@ -1363,17 +1529,18 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + debugInfo, ), ); case REACT_LAZY_TYPE: const payload = newChild._payload; const init = newChild._init; - // TODO: This function is supposed to be non-recursive. - return reconcileChildFibers( + return reconcileChildFibersImpl( returnFiber, currentFirstChild, init(payload), lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } @@ -1383,6 +1550,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } @@ -1392,6 +1560,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } @@ -1418,6 +1587,7 @@ function createChildReconciler( currentFirstChild, unwrapThenable(thenable), lanes, + mergeDebugInfo(debugInfo, thenable._debugInfo), ); } @@ -1428,6 +1598,7 @@ function createChildReconciler( currentFirstChild, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -1472,6 +1643,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + null, // debugInfo ); thenableState = null; // Don't bother to reset `thenableIndexCounter` to 0 because it always gets diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 4a333ad782f1b..3bd5253e71d02 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -3760,6 +3760,10 @@ function remountFiber( newWorkInProgress.return = oldWorkInProgress.return; newWorkInProgress.ref = oldWorkInProgress.ref; + if (__DEV__) { + newWorkInProgress._debugInfo = oldWorkInProgress._debugInfo; + } + // Replace the child/sibling pointers above it. if (oldWorkInProgress === returnFiber.child) { returnFiber.child = newWorkInProgress; diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index d89981e4d78cc..27a09118988e0 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -55,7 +55,6 @@ import type { ReactDebugInfo, ReactComponentInfo, ReactAsyncInfo, - ReactConsoleInfo, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 577930a39ac31..107a6082f8e58 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -111,20 +111,24 @@ interface ThenableImpl { } interface UntrackedThenable extends ThenableImpl { status?: void; + _debugInfo?: null | ReactDebugInfo; } export interface PendingThenable extends ThenableImpl { status: 'pending'; + _debugInfo?: null | ReactDebugInfo; } export interface FulfilledThenable extends ThenableImpl { status: 'fulfilled'; value: T; + _debugInfo?: null | ReactDebugInfo; } export interface RejectedThenable extends ThenableImpl { status: 'rejected'; reason: mixed; + _debugInfo?: null | ReactDebugInfo; } export type Thenable = @@ -185,6 +189,4 @@ export type ReactAsyncInfo = { +stack?: string, }; -export type ReactDebugInfo = Array< - ReactComponentInfo | ReactAsyncInfo, ->; +export type ReactDebugInfo = Array;