Skip to content

Commit cb5ea03

Browse files
SamChou19815meta-codesync[bot]
authored andcommitted
Add LegacyFunction to preserve callback compatibility (#59096)
Summary: Pull Request resolved: #59096 With `experimental.function_and_object_type_is_no_longer_any`, Flow resolves `Function` to the builtin class type instead of `any`. Add `LegacyFunction` as a global compat type, following `LegacyObject`, and use it for the legacy callbacks in `Alert`, `Animated`, `PushNotificationIOS`, and `Dimensions`. The TypeScript API pre-transform replaces `LegacyFunction` with `Function`, preserving the generated public API. Remove the `unclear-type` suppressions made unnecessary by the compat type. Changelog: [Internal] Reviewed By: panagosg7 Differential Revision: D125351850 fbshipit-source-id: 8d7003a908982f57a057ff0e2b5c023312d57d11
1 parent 80ca42e commit cb5ea03

8 files changed

Lines changed: 120 additions & 20 deletions

File tree

‎packages/react-native/Libraries/Alert/Alert.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
2727
export type AlertButton = {
2828
text?: string,
2929
// $FlowFixMe[unclear-type]
30-
onPress?: ?((value?: string) => any) | ?Function,
30+
onPress?: ?((value?: string) => any) | ?LegacyFunction,
3131
isPreferred?: boolean,
3232
style?: AlertButtonStyle,
3333
...

‎packages/react-native/Libraries/Animated/AnimatedImplementation.js‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -559,12 +559,9 @@ const loopImpl = function (
559559
};
560560

561561
function forkEventImpl(
562-
// $FlowFixMe[unclear-type]
563-
event: ?AnimatedEvent | ?Function,
564-
// $FlowFixMe[unclear-type]
565-
listener: Function,
566-
// $FlowFixMe[unclear-type]
567-
): AnimatedEvent | Function {
562+
event: ?AnimatedEvent | ?LegacyFunction,
563+
listener: LegacyFunction,
564+
): AnimatedEvent | LegacyFunction {
568565
if (!event) {
569566
return listener;
570567
} else if (event instanceof AnimatedEvent) {
@@ -579,10 +576,8 @@ function forkEventImpl(
579576
}
580577

581578
function unforkEventImpl(
582-
// $FlowFixMe[unclear-type]
583-
event: ?AnimatedEvent | ?Function,
584-
// $FlowFixMe[unclear-type]
585-
listener: Function,
579+
event: ?AnimatedEvent | ?LegacyFunction,
580+
listener: LegacyFunction,
586581
): void {
587582
if (event && event instanceof AnimatedEvent) {
588583
event.__removeListener(listener);

‎packages/react-native/Libraries/PushNotificationIOS/PushNotificationIOS.js‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,7 @@ class PushNotificationIOS {
319319
*
320320
* See https://reactnative.dev/docs/pushnotificationios#getapplicationiconbadgenumber
321321
*/
322-
// $FlowFixMe[unclear-type]
323-
static getApplicationIconBadgeNumber(callback: Function): void {
322+
static getApplicationIconBadgeNumber(callback: LegacyFunction): void {
324323
invariant(
325324
NativePushNotificationManagerIOS,
326325
'PushNotificationManager is not available.',
@@ -348,8 +347,7 @@ class PushNotificationIOS {
348347
*
349348
* See https://reactnative.dev/docs/pushnotificationios#getscheduledlocalnotifications
350349
*/
351-
// $FlowFixMe[unclear-type]
352-
static getScheduledLocalNotifications(callback: Function): void {
350+
static getScheduledLocalNotifications(callback: LegacyFunction): void {
353351
invariant(
354352
NativePushNotificationManagerIOS,
355353
'PushNotificationManager is not available.',
@@ -365,8 +363,7 @@ class PushNotificationIOS {
365363
*/
366364
static addEventListener(
367365
type: PushNotificationEventName,
368-
// $FlowFixMe[unclear-type]
369-
handler: Function,
366+
handler: LegacyFunction,
370367
): void {
371368
invariant(
372369
type === 'notification' ||

‎packages/react-native/Libraries/Utilities/Dimensions.js‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,7 @@ class Dimensions {
111111
*/
112112
static addEventListener(
113113
type: 'change',
114-
/* $FlowFixMe[unclear-type] Callers annotate the payload with assorted
115-
* object shapes that any precise signature would reject. */
116-
handler: Function,
114+
handler: LegacyFunction,
117115
): EventSubscription {
118116
invariant(
119117
type === 'change',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
* @format
9+
*/
10+
11+
// The legacy Flow `Function` type, which is `any`. The generated TypeScript API
12+
// (scripts/js-api/build-types) translates this type to `Function`.
13+
declare type LegacyFunction = any;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
const translateSourceFile = require('../../../translateSourceFile');
12+
const replaceLegacyFunctionWithFunction = require('../replaceLegacyFunctionWithFunction');
13+
const {parse, print} = require('flow-transform');
14+
15+
const prettierOptions = {parser: 'babel'};
16+
17+
async function translate(code: string): Promise<string> {
18+
const parsed = await parse(code);
19+
const result = await replaceLegacyFunctionWithFunction(parsed);
20+
return print(result.ast, result.mutatedCode, prettierOptions);
21+
}
22+
23+
describe('replaceLegacyFunctionWithFunction', () => {
24+
test('replaces LegacyFunction in return annotations', async () => {
25+
expect(await translate('function foo(): LegacyFunction {}')).toBe(
26+
'function foo(): Function {}\n',
27+
);
28+
});
29+
30+
test('replaces LegacyFunction in nullable unions', async () => {
31+
expect(
32+
await translate('let foo: ?((value?: string) => any) | ?LegacyFunction;'),
33+
).toBe('let foo: ?((value?: string) => any) | ?Function;\n');
34+
});
35+
36+
test('replaces LegacyFunction in type aliases', async () => {
37+
expect(await translate('type Foo = LegacyFunction;')).toBe(
38+
'type Foo = Function;\n',
39+
);
40+
});
41+
42+
test('preserves unrelated and qualified types', async () => {
43+
const code = 'type Foo = Function | Other.LegacyFunction;';
44+
expect(await translate(code)).toBe(code + '\n');
45+
});
46+
47+
test('preserves Function in the generated TypeScript API', async () => {
48+
const source = `export type Callbacks = {
49+
onPress?: ?((value?: string) => any) | ?LegacyFunction,
50+
listener: LegacyFunction,
51+
};
52+
export function forkEvent(event: ?LegacyFunction): LegacyFunction {
53+
return event;
54+
}`;
55+
const result = await translateSourceFile(source, __filename);
56+
const original = await translateSourceFile(
57+
source.replaceAll('LegacyFunction', 'Function'),
58+
__filename,
59+
);
60+
expect(result).toEqual(original);
61+
});
62+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import type {TransformVisitor} from 'flow-transform';
12+
import type {ParseResult} from 'flow-transform/dist/transform/parse';
13+
import type {TransformASTResult} from 'flow-transform/dist/transform/transformAST';
14+
15+
const {transformAST} = require('flow-transform/dist/transform/transformAST');
16+
17+
const visitors: TransformVisitor = context => ({
18+
GenericTypeAnnotation(node): void {
19+
if (node.id.type === 'Identifier' && node.id.name === 'LegacyFunction') {
20+
context.modifyNodeInPlace(node.id, {name: 'Function'});
21+
}
22+
},
23+
});
24+
25+
/**
26+
* Replaces LegacyFunction with Function to preserve the TypeScript API.
27+
*/
28+
async function replaceLegacyFunctionWithFunction(
29+
source: ParseResult,
30+
): Promise<TransformASTResult> {
31+
return transformAST(source, visitors);
32+
}
33+
34+
module.exports = replaceLegacyFunctionWithFunction;

‎scripts/js-api/build-types/translateSourceFile.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const preTransforms: Array<PreTransformFn> = [
2424
require('./transforms/flow/replaceRequiresWithImports'),
2525
require('./transforms/flow/replaceEmptyWithNever'),
2626
require('./transforms/flow/replaceStringishWithString'),
27+
require('./transforms/flow/replaceLegacyFunctionWithFunction'),
2728
require('./transforms/flow/replaceLegacyObjectWithObject'),
2829
require('./transforms/flow/replaceNullablePropertiesWithUndefined'),
2930
require('./transforms/flow/reattachDocComments'),

0 commit comments

Comments
 (0)