Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
[ghstack-poisoned]
  • Loading branch information
mofeiZ committed Oct 1, 2024
2 parents 9119289 + a9b7a83 commit 48fb60a
Show file tree
Hide file tree
Showing 18 changed files with 402 additions and 868 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ type OptionalTraversalContext = {
hoistableObjects: Map<BlockId, ReactiveScopeDependency>;
};

/**
* Match the consequent and alternate blocks of an optional.
* @returns propertyload computed by the consequent block, or null if the
* consequent block is not a simple PropertyLoad.
*/
function matchOptionalTestBlock(
terminal: BranchTerminal,
blocks: ReadonlyMap<BlockId, BasicBlock>,
Expand Down Expand Up @@ -168,7 +173,18 @@ function matchOptionalTestBlock(
) {
return null;
}
assertOptionalAlternateBlock(terminal, blocks);
const alternate = assertNonNull(blocks.get(terminal.alternate));

CompilerError.invariant(
alternate.instructions.length === 2 &&
alternate.instructions[0].value.kind === 'Primitive' &&
alternate.instructions[1].value.kind === 'StoreLocal',
{
reason: 'Unexpected alternate structure',
loc: terminal.loc,
},
);

return {
consequentId: storeLocal.lvalue.place.identifier.id,
property: propertyLoad.value.property,
Expand All @@ -180,23 +196,6 @@ function matchOptionalTestBlock(
return null;
}

function assertOptionalAlternateBlock(
terminal: BranchTerminal,
blocks: ReadonlyMap<BlockId, BasicBlock>,
): void {
const alternate = assertNonNull(blocks.get(terminal.alternate));

CompilerError.invariant(
alternate.instructions.length === 2 &&
alternate.instructions[0].value.kind === 'Primitive' &&
alternate.instructions[1].value.kind === 'StoreLocal',
{
reason: 'Unexpected alternate structure',
loc: terminal.loc,
},
);
}

/**
* Traverse into the optional block and all transitively referenced blocks to
* collect sidemaps of optional chain dependencies.
Expand All @@ -216,23 +215,21 @@ function traverseOptionalBlock(
let test: BranchTerminal;
let baseObject: ReactiveScopeDependency;
if (maybeTest.terminal.kind === 'branch') {
CompilerError.invariant(optional.terminal.optional, {
reason: '[OptionalChainDeps] Expect base case to be always optional',
loc: optional.terminal.loc,
});
/**
* Explicitly calculate base of load
*
* Optional base expressions are currently within value blocks which cannot
* be interrupted by scope boundaries. As such, the only dependencies we can
* hoist out of optional chains are property load chains with no intervening
* instructions.
*
* Ideally, we would be able to flatten base instructions out of optional
* blocks, but this would require changes to HIR.
*/
CompilerError.invariant(optional.terminal.optional, {
reason: '[OptionalChainDeps] Expect base case to be always optional',
loc: optional.terminal.loc,
});
/**
* Only match base expressions that are straightforward PropertyLoad chains
*
* For now, only match base expressions that are straightforward
* PropertyLoad chains
*/
if (
maybeTest.instructions.length === 0 ||
Expand Down
95 changes: 52 additions & 43 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,21 @@ function parseModelString(
createFormData,
);
}
case 'Z': {
// Error
if (__DEV__) {
const ref = value.slice(2);
return getOutlinedModel(
response,
ref,
parentObject,
key,
resolveErrorDev,
);
} else {
return resolveErrorProd(response);
}
}
case 'i': {
// Iterator
const ref = value.slice(2);
Expand Down Expand Up @@ -1881,11 +1896,7 @@ function formatV8Stack(
}

type ErrorWithDigest = Error & {digest?: string};
function resolveErrorProd(
response: Response,
id: number,
digest: string,
): void {
function resolveErrorProd(response: Response): Error {
if (__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
Expand All @@ -1899,25 +1910,17 @@ function resolveErrorProd(
' may provide additional details about the nature of the error.',
);
error.stack = 'Error: ' + error.message;
(error: any).digest = digest;
const errorWithDigest: ErrorWithDigest = (error: any);
const chunks = response._chunks;
const chunk = chunks.get(id);
if (!chunk) {
chunks.set(id, createErrorChunk(response, errorWithDigest));
} else {
triggerErrorOnChunk(chunk, errorWithDigest);
}
return error;
}

function resolveErrorDev(
response: Response,
id: number,
digest: string,
message: string,
stack: ReactStackTrace,
env: string,
): void {
errorInfo: {message: string, stack: ReactStackTrace, env: string, ...},
): Error {
const message: string = errorInfo.message;
const stack: ReactStackTrace = errorInfo.stack;
const env: string = errorInfo.env;

if (!__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
Expand Down Expand Up @@ -1957,16 +1960,8 @@ function resolveErrorDev(
}
}

(error: any).digest = digest;
(error: any).environmentName = env;
const errorWithDigest: ErrorWithDigest = (error: any);
const chunks = response._chunks;
const chunk = chunks.get(id);
if (!chunk) {
chunks.set(id, createErrorChunk(response, errorWithDigest));
} else {
triggerErrorOnChunk(chunk, errorWithDigest);
}
return error;
}

function resolvePostponeProd(response: Response, id: number): void {
Expand Down Expand Up @@ -2622,17 +2617,20 @@ function processFullStringRow(
}
case 69 /* "E" */: {
const errorInfo = JSON.parse(row);
let error;
if (__DEV__) {
resolveErrorDev(
response,
id,
errorInfo.digest,
errorInfo.message,
errorInfo.stack,
errorInfo.env,
);
error = resolveErrorDev(response, errorInfo);
} else {
error = resolveErrorProd(response);
}
(error: any).digest = errorInfo.digest;
const errorWithDigest: ErrorWithDigest = (error: any);
const chunks = response._chunks;
const chunk = chunks.get(id);
if (!chunk) {
chunks.set(id, createErrorChunk(response, errorWithDigest));
} else {
resolveErrorProd(response, id, errorInfo.digest);
triggerErrorOnChunk(chunk, errorWithDigest);
}
return;
}
Expand All @@ -2642,11 +2640,22 @@ function processFullStringRow(
}
case 68 /* "D" */: {
if (__DEV__) {
const debugInfo: ReactComponentInfo | ReactAsyncInfo = parseModel(
response,
row,
);
resolveDebugInfo(response, id, debugInfo);
const chunk: ResolvedModelChunk<ReactComponentInfo | ReactAsyncInfo> =
createResolvedModelChunk(response, row);
initializeModelChunk(chunk);
const initializedChunk: SomeChunk<ReactComponentInfo | ReactAsyncInfo> =
chunk;
if (initializedChunk.status === INITIALIZED) {
resolveDebugInfo(response, id, initializedChunk.value);
} else {
// TODO: This is not going to resolve in the right order if there's more than one.
chunk.then(
v => resolveDebugInfo(response, id, v),
e => {
// Ignore debug info errors for now. Unnecessary noise.
},
);
}
return;
}
// Fallthrough to share the error with Console entries.
Expand Down
73 changes: 73 additions & 0 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {
firstName: 'Seb',
lastName: 'Smith',
},
},
]
: undefined,
Expand Down Expand Up @@ -347,6 +351,10 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {
firstName: 'Seb',
lastName: 'Smith',
},
},
]
: undefined,
Expand Down Expand Up @@ -653,6 +661,46 @@ describe('ReactFlight', () => {
`);
});

it('can transport Error objects as values', async () => {
function ComponentClient({prop}) {
return `
is error: ${prop instanceof Error}
message: ${prop.message}
stack: ${normalizeCodeLocInfo(prop.stack).split('\n').slice(0, 2).join('\n')}
environmentName: ${prop.environmentName}
`;
}
const Component = clientReference(ComponentClient);

function ServerComponent() {
const error = new Error('hello');
return <Component prop={error} />;
}

const transport = ReactNoopFlightServer.render(<ServerComponent />);

await act(async () => {
ReactNoop.render(await ReactNoopFlightClient.read(transport));
});

if (__DEV__) {
expect(ReactNoop).toMatchRenderedOutput(`
is error: true
message: hello
stack: Error: hello
in ServerComponent (at **)
environmentName: Server
`);
} else {
expect(ReactNoop).toMatchRenderedOutput(`
is error: true
message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
stack: Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
environmentName: undefined
`);
}
});

it('can transport cyclic objects', async () => {
function ComponentClient({prop}) {
expect(prop.obj.obj.obj).toBe(prop.obj.obj);
Expand Down Expand Up @@ -2625,6 +2673,9 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {
transport: expect.arrayContaining([]),
},
},
]
: undefined,
Expand All @@ -2643,6 +2694,7 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {},
},
]
: undefined,
Expand All @@ -2658,6 +2710,7 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in myLazy (at **)\n in lazyInitializer (at **)'
: undefined,
props: {},
},
]
: undefined,
Expand All @@ -2673,6 +2726,7 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {},
},
]
: undefined,
Expand Down Expand Up @@ -2747,6 +2801,9 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {
transport: expect.arrayContaining([]),
},
},
]
: undefined,
Expand All @@ -2764,6 +2821,9 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in ServerComponent (at **)'
: undefined,
props: {
children: {},
},
},
]
: undefined,
Expand All @@ -2780,6 +2840,7 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {},
},
]
: undefined,
Expand Down Expand Up @@ -2938,6 +2999,7 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {},
},
{
env: 'B',
Expand Down Expand Up @@ -3068,6 +3130,9 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Object.<anonymous> (at **)'
: undefined,
props: {
firstName: 'Seb',
},
};
expect(getDebugInfo(greeting)).toEqual([
greetInfo,
Expand All @@ -3079,6 +3144,14 @@ describe('ReactFlight', () => {
stack: gate(flag => flag.enableOwnerStacks)
? ' in Greeting (at **)'
: undefined,
props: {
children: expect.objectContaining({
type: 'span',
props: {
children: ['Hello, ', 'Seb'],
},
}),
},
},
]);
// The owner that created the span was the outer server component.
Expand Down
Loading

0 comments on commit 48fb60a

Please sign in to comment.