[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:
Joe Savona
2026-02-25 13:22:30 -08:00
parent 074d96b9dd
commit 49bbeb91e5
5 changed files with 529 additions and 7 deletions

View File

@@ -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) {

View File

@@ -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
];

View File

@@ -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,
[],

View File

@@ -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>

View File

@@ -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')}],
};