From ffc250ec982a7b456eb2962122b5161c05b08a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Mon, 12 Feb 2024 14:56:59 -0500 Subject: [PATCH] [Fiber] Transfer `_debugInfo` from Arrays, Lazy, Thenables and Elements to the inner Fibers. (#28286) That way we can use it for debug information like component stacks and DevTools. I used an extra stack argument in Child Fiber to track this as it's flowing down since it's not just elements where we have this info readily available but parent arrays and lazy can merge this into the Fiber too. It's not great that this is a dev-only argument and I could track it globally but seems more likely to make mistakes. It is possible for the same debug info to appear for multiple child fibers like when it's attached to a fragment or a lazy that resolves to a fragment at the root. The object identity could be used in these scenarios to infer if that's really one server component that's a parent of all children or if each child has a server component with the same name. This is effectively a public API because you can use it to stash information on Promises from a third-party service - not just Server Components. I started outline the types for this for some things I was planning to add but it's not final. I was also planning on storing it from `use(thenable)` for when you suspend on a Promise. However, I realized that there's no Hook instance for those to stash it on. So it might need a separate data structure to stash the previous pass over of `use()` that resets each render. No tests yet since I didn't want to test internals but it'll be covered once we have debugging features like component stacks. --- .../react-client/src/ReactFlightClient.js | 12 +- .../react-reconciler/src/ReactChildFiber.js | 206 ++++++++++++++++-- packages/react-reconciler/src/ReactFiber.js | 4 +- .../src/ReactFiberBeginWork.js | 4 + .../src/ReactInternalTypes.js | 2 + .../react-server/src/ReactFlightServer.js | 8 +- packages/react/src/ReactLazy.js | 4 +- packages/shared/ReactTypes.js | 17 ++ 8 files changed, 228 insertions(+), 29 deletions(-) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 46e09b1cf0274..8cb6606912980 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -7,7 +7,12 @@ * @flow */ -import type {Thenable} from 'shared/ReactTypes'; +import type { + Thenable, + ReactDebugInfo, + ReactComponentInfo, + ReactAsyncInfo, +} from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; import type { @@ -76,9 +81,6 @@ const RESOLVED_MODULE = 'resolved_module'; const INITIALIZED = 'fulfilled'; const ERRORED = 'rejected'; -// Dev-only -type ReactDebugInfo = Array<{+name?: string, +env?: string}>; - type PendingChunk = { status: 'pending', value: null | Array<(T) => mixed>, @@ -1014,7 +1016,7 @@ function resolveHint( function resolveDebugInfo( response: Response, id: number, - debugInfo: {name: string}, + debugInfo: ReactComponentInfo | ReactAsyncInfo, ): void { if (!__DEV__) { // These errors should never make it into a build so we don't need to encode them in codes.json 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/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 8a01d87b8ff7c..82a07c2bbcf24 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -201,7 +201,7 @@ function FiberNode( if (__DEV__) { // This isn't directly used but is handy for debugging internals: - + this._debugInfo = null; this._debugOwner = null; this._debugNeedsRemount = false; this._debugHookTypes = null; @@ -347,6 +347,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { } if (__DEV__) { + workInProgress._debugInfo = current._debugInfo; workInProgress._debugNeedsRemount = current._debugNeedsRemount; switch (workInProgress.tag) { case IndeterminateComponent: @@ -911,6 +912,7 @@ export function assignFiberPropertiesInDEV( target.treeBaseDuration = source.treeBaseDuration; } + target._debugInfo = source._debugInfo; target._debugOwner = source._debugOwner; target._debugNeedsRemount = source._debugNeedsRemount; target._debugHookTypes = source._debugHookTypes; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 8b93630396666..8fd639322e46b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -3761,6 +3761,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-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index f4ec8ab186a05..126bb02cfdd7e 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -15,6 +15,7 @@ import type { Usable, ReactFormState, Awaited, + ReactDebugInfo, } from 'shared/ReactTypes'; import type {WorkTag} from './ReactWorkTags'; import type {TypeOfMode} from './ReactTypeOfMode'; @@ -199,6 +200,7 @@ export type Fiber = { // to be the same as work in progress. // __DEV__ only + _debugInfo?: ReactDebugInfo | null, _debugOwner?: Fiber | null, _debugIsCurrentlyTiming?: boolean, _debugNeedsRemount?: boolean, diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index e0d26b2d30473..1d1ca9891135d 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -52,6 +52,9 @@ import type { PendingThenable, FulfilledThenable, RejectedThenable, + ReactDebugInfo, + ReactComponentInfo, + ReactAsyncInfo, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; @@ -107,9 +110,6 @@ import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable'; initAsyncDebugInfo(); -// Dev-only -type ReactDebugInfo = Array<{+name?: string, +env?: string}>; - const ObjectPrototype = Object.prototype; type JSONValue = @@ -1731,7 +1731,7 @@ function emitModelChunk(request: Request, id: number, json: string): void { function emitDebugChunk( request: Request, id: number, - debugInfo: {+name?: string, +env?: string}, + debugInfo: ReactComponentInfo | ReactAsyncInfo, ): void { if (!__DEV__) { // These errors should never make it into a build so we don't need to encode them in codes.json diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js index ece18edca40a3..15417c2e763d0 100644 --- a/packages/react/src/ReactLazy.js +++ b/packages/react/src/ReactLazy.js @@ -7,7 +7,7 @@ * @flow */ -import type {Wakeable, Thenable} from 'shared/ReactTypes'; +import type {Wakeable, Thenable, ReactDebugInfo} from 'shared/ReactTypes'; import {REACT_LAZY_TYPE} from 'shared/ReactSymbols'; @@ -46,7 +46,7 @@ export type LazyComponent = { $$typeof: symbol | number, _payload: P, _init: (payload: P) => T, - _debugInfo?: null | Array<{+name?: string, +env?: string}>, + _debugInfo?: null | ReactDebugInfo, }; function lazyInitializer(payload: Payload): T { diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 1fe711b779bf3..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 = @@ -173,3 +177,16 @@ export type Awaited = T extends null | void : empty // the argument to `then` was not callable. : T // argument was not an object : T; // non-thenable + +export type ReactComponentInfo = { + +name?: string, + +env?: string, +}; + +export type ReactAsyncInfo = { + +started?: number, + +completed?: number, + +stack?: string, +}; + +export type ReactDebugInfo = Array;