mirror of
https://github.com/facebook/react.git
synced 2026-02-26 02:54:59 +00:00
[compiler] Add Intl formatter types and fix moduleTypeProvider fallback
Add type definitions for all Intl formatter objects (DateTimeFormat, NumberFormat, Collator, PluralRules, ListFormat, RelativeTimeFormat, Segmenter, DisplayNames) so the compiler understands that formatter instances are immutable and their methods only read arguments. Without these types, `new Intl.DateTimeFormat().format(date)` would conservatively assume the format call captures `date` into the formatter, creating an unnecessary dependency. Also fix `#resolveModuleType` to always fall back to `defaultModuleTypeProvider` when a custom `moduleTypeProvider` returns null, so that tools like the snap runner that set their own provider still get the default module types (react-hook-form, tanstack, etc.).
This commit is contained in:
@@ -792,18 +792,20 @@ export class Environment {
|
||||
* NOTE: Zod doesn't work when specifying a function as a default, so we have to
|
||||
* fallback to the default value here
|
||||
*/
|
||||
const moduleTypeProvider =
|
||||
this.config.moduleTypeProvider ?? defaultModuleTypeProvider;
|
||||
if (moduleTypeProvider == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof moduleTypeProvider !== 'function') {
|
||||
const moduleTypeProvider = this.config.moduleTypeProvider;
|
||||
if (
|
||||
moduleTypeProvider != null &&
|
||||
typeof moduleTypeProvider !== 'function'
|
||||
) {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason: `Expected a function for \`moduleTypeProvider\``,
|
||||
loc,
|
||||
});
|
||||
}
|
||||
const unparsedModuleConfig = moduleTypeProvider(moduleName);
|
||||
const unparsedModuleConfig =
|
||||
(typeof moduleTypeProvider === 'function'
|
||||
? moduleTypeProvider(moduleName)
|
||||
: null) ?? defaultModuleTypeProvider(moduleName);
|
||||
if (unparsedModuleConfig != null) {
|
||||
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
|
||||
if (!parsedModuleConfig.success) {
|
||||
|
||||
@@ -28,6 +28,14 @@ import {
|
||||
BuiltInWeakMapId,
|
||||
BuiltInWeakSetId,
|
||||
BuiltInEffectEventId,
|
||||
BuiltInIntlDateTimeFormatId,
|
||||
BuiltInIntlNumberFormatId,
|
||||
BuiltInIntlCollatorId,
|
||||
BuiltInIntlPluralRulesId,
|
||||
BuiltInIntlListFormatId,
|
||||
BuiltInIntlRelativeTimeFormatId,
|
||||
BuiltInIntlSegmenterId,
|
||||
BuiltInIntlDisplayNamesId,
|
||||
ReanimatedSharedValueId,
|
||||
ShapeRegistry,
|
||||
addFunction,
|
||||
@@ -620,6 +628,145 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'Intl',
|
||||
addObject(DEFAULT_SHAPES, 'Intl', [
|
||||
[
|
||||
'DateTimeFormat',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {
|
||||
kind: 'Object',
|
||||
shapeId: BuiltInIntlDateTimeFormatId,
|
||||
},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'NumberFormat',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInIntlNumberFormatId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'Collator',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInIntlCollatorId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'PluralRules',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInIntlPluralRulesId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'ListFormat',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInIntlListFormatId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'RelativeTimeFormat',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {
|
||||
kind: 'Object',
|
||||
shapeId: BuiltInIntlRelativeTimeFormatId,
|
||||
},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'Segmenter',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInIntlSegmenterId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
[
|
||||
'DisplayNames',
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInIntlDisplayNamesId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
null,
|
||||
true,
|
||||
),
|
||||
],
|
||||
]),
|
||||
],
|
||||
// TODO: rest of Global objects
|
||||
];
|
||||
|
||||
|
||||
@@ -389,6 +389,16 @@ export const BuiltInEffectEventId = 'BuiltInEffectEventFunction';
|
||||
// See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types
|
||||
export const ReanimatedSharedValueId = 'ReanimatedSharedValueId';
|
||||
|
||||
// Intl formatter instance shapes
|
||||
export const BuiltInIntlDateTimeFormatId = 'BuiltInIntlDateTimeFormat';
|
||||
export const BuiltInIntlNumberFormatId = 'BuiltInIntlNumberFormat';
|
||||
export const BuiltInIntlCollatorId = 'BuiltInIntlCollator';
|
||||
export const BuiltInIntlPluralRulesId = 'BuiltInIntlPluralRules';
|
||||
export const BuiltInIntlListFormatId = 'BuiltInIntlListFormat';
|
||||
export const BuiltInIntlRelativeTimeFormatId = 'BuiltInIntlRelativeTimeFormat';
|
||||
export const BuiltInIntlSegmenterId = 'BuiltInIntlSegmenter';
|
||||
export const BuiltInIntlDisplayNamesId = 'BuiltInIntlDisplayNames';
|
||||
|
||||
// ShapeRegistry with default definitions for built-ins.
|
||||
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
|
||||
|
||||
@@ -1232,6 +1242,297 @@ addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
|
||||
|
||||
addObject(BUILTIN_SHAPES, ReanimatedSharedValueId, []);
|
||||
|
||||
/**
|
||||
* Intl formatter instance shapes.
|
||||
*
|
||||
* All Intl formatter objects are immutable after construction — calling their
|
||||
* methods does not modify the formatter. Methods like `format()` return
|
||||
* primitives (strings/numbers), `formatToParts()` returns a new array, and
|
||||
* `resolvedOptions()` returns a new object.
|
||||
*/
|
||||
|
||||
/* Intl.DateTimeFormat instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlDateTimeFormatId, [
|
||||
[
|
||||
'format',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatToParts',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatRange',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatRangeToParts',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.NumberFormat instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlNumberFormatId, [
|
||||
[
|
||||
'format',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatToParts',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatRange',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatRangeToParts',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.Collator instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlCollatorId, [
|
||||
[
|
||||
'compare',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.PluralRules instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlPluralRulesId, [
|
||||
[
|
||||
'select',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'selectRange',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.ListFormat instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlListFormatId, [
|
||||
[
|
||||
'format',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatToParts',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.RelativeTimeFormat instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlRelativeTimeFormatId, [
|
||||
[
|
||||
'format',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'formatToParts',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read, Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.Segmenter instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlSegmenterId, [
|
||||
[
|
||||
'segment',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
/* Intl.DisplayNames instance */
|
||||
addObject(BUILTIN_SHAPES, BuiltInIntlDisplayNamesId, [
|
||||
[
|
||||
'of',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'resolvedOptions',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
|
||||
addFunction(
|
||||
BUILTIN_SHAPES,
|
||||
[],
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function DateComponent({date}) {
|
||||
const formatter = new Intl.DateTimeFormat('en-US');
|
||||
|
||||
return <time dateTime={date.toISOString()}>{formatter.format(date)}</time>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: DateComponent,
|
||||
params: [{date: new Date('2024-01-01')}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function DateComponent(t0) {
|
||||
const $ = _c(6);
|
||||
const { date } = t0;
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = new Intl.DateTimeFormat("en-US");
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
const formatter = t1;
|
||||
let t2;
|
||||
if ($[1] !== date) {
|
||||
t2 = date.toISOString();
|
||||
$[1] = date;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
const t3 = formatter.format(date);
|
||||
let t4;
|
||||
if ($[3] !== t2 || $[4] !== t3) {
|
||||
t4 = <time dateTime={t2}>{t3}</time>;
|
||||
$[3] = t2;
|
||||
$[4] = t3;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: DateComponent,
|
||||
params: [{ date: new Date("2024-01-01") }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <time datetime="2024-01-01T00:00:00.000Z">12/31/2023</time>
|
||||
@@ -0,0 +1,10 @@
|
||||
function DateComponent({date}) {
|
||||
const formatter = new Intl.DateTimeFormat('en-US');
|
||||
|
||||
return <time dateTime={date.toISOString()}>{formatter.format(date)}</time>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: DateComponent,
|
||||
params: [{date: new Date('2024-01-01')}],
|
||||
};
|
||||
Reference in New Issue
Block a user