Skip to content

Make OpaqueNativeObserverHandle really opaque #52054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -18,7 +18,10 @@ const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js')
const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js');
const {compareSnaps, compareTsArraySnaps} = require('../compareSnaps.js');

const flowExtraCases = ['PROMISE_WITH_COMMONLY_USED_TYPES'];
const flowExtraCases = [
'NATIVE_MODULE_WITH_OPAQUE_TYPES',
'PROMISE_WITH_COMMONLY_USED_TYPES',
];
const tsExtraCases = [
'NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS',
'NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE',
Original file line number Diff line number Diff line change
@@ -691,6 +691,37 @@ export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');

`;

const NATIVE_MODULE_WITH_OPAQUE_TYPES = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

'use strict';

import type {TurboModule} from '../RCTExport';
import * as TurboModuleRegistry from '../TurboModuleRegistry';

export opaque type Task = mixed;
export opaque type TimeoutID = number;

export interface Spec extends TurboModule {
+createTask: (callback: () => void) => Task;
+cancelTask: (task: Task) => void;

+setTimeout: (callback: () => void) => TimeoutID;
+clearTimeout: (timeoutID: TimeoutID) => void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');

`;

const ANDROID_ONLY_NATIVE_MODULE = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -976,6 +1007,7 @@ module.exports = {
NATIVE_MODULE_WITH_UNION,
NATIVE_MODULE_WITH_UNION_RETURN_TYPES,
NATIVE_MODULE_WITH_EVENT_EMITTERS,
NATIVE_MODULE_WITH_OPAQUE_TYPES,
EMPTY_NATIVE_MODULE,
ANDROID_ONLY_NATIVE_MODULE,
IOS_ONLY_NATIVE_MODULE,
Original file line number Diff line number Diff line change
@@ -2187,6 +2187,108 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W
}"
`;

exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OPAQUE_TYPES 1`] = `
"{
'modules': {
'NativeSampleTurboModule': {
'type': 'NativeModule',
'aliasMap': {},
'enumMap': {},
'spec': {
'eventEmitters': [],
'methods': [
{
'name': 'createTask',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'GenericObjectTypeAnnotation'
},
'params': [
{
'name': 'callback',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': []
}
}
]
}
},
{
'name': 'cancelTask',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': [
{
'name': 'task',
'optional': false,
'typeAnnotation': {
'type': 'GenericObjectTypeAnnotation'
}
}
]
}
},
{
'name': 'setTimeout',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'NumberTypeAnnotation'
},
'params': [
{
'name': 'callback',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': []
}
}
]
}
},
{
'name': 'clearTimeout',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'VoidTypeAnnotation'
},
'params': [
{
'name': 'timeoutID',
'optional': false,
'typeAnnotation': {
'type': 'NumberTypeAnnotation'
}
}
]
}
}
]
},
'moduleName': 'SampleTurboModule'
}
}
}"
`;

exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PARTIALS 1`] = `
"{
'modules': {
6 changes: 6 additions & 0 deletions packages/react-native-codegen/src/parsers/flow/parser.js
Original file line number Diff line number Diff line change
@@ -296,6 +296,7 @@ class FlowParser implements Parser {
if (
node.declaration != null &&
(node.declaration.type === 'TypeAlias' ||
node.declaration.type === 'OpaqueType' ||
node.declaration.type === 'InterfaceDeclaration')
) {
types[node.declaration.id.name] = node.declaration;
@@ -309,6 +310,7 @@ class FlowParser implements Parser {
types[node.declaration.id.name] = node.declaration;
} else if (
node.type === 'TypeAlias' ||
node.type === 'OpaqueType' ||
node.type === 'InterfaceDeclaration' ||
node.type === 'EnumDeclaration'
) {
@@ -543,6 +545,10 @@ class FlowParser implements Parser {
}

nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe {
if (typeAnnotation.type === 'OpaqueType') {
return typeAnnotation.impltype;
}

return typeAnnotation.right;
}

3 changes: 2 additions & 1 deletion packages/react-native-codegen/src/parsers/parsers-commons.js
Original file line number Diff line number Diff line change
@@ -1233,7 +1233,8 @@ function handleGenericTypeAnnotation(
let node;

switch (resolvedTypeAnnotation.type) {
case parser.typeAlias: {
case parser.typeAlias:
case 'OpaqueType': {
typeResolutionStatus = getTypeResolutionStatus(
'alias',
typeAnnotation,
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@ import type {TurboModule} from '../../../../../Libraries/TurboModule/RCTExport';

import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/TurboModuleRegistry';

export opaque type IdleCallbackID = mixed;

export type RequestIdleCallbackOptions = {
timeout?: number,
};
@@ -25,8 +27,8 @@ export interface Spec extends TurboModule {
+requestIdleCallback: (
callback: (idleDeadline: IdleDeadline) => mixed,
options?: RequestIdleCallbackOptions,
) => mixed;
+cancelIdleCallback: (handle: mixed) => void;
) => IdleCallbackID;
+cancelIdleCallback: (handle: IdleCallbackID) => void;
}

export default (TurboModuleRegistry.getEnforcing<Spec>(
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ export type NativeIntersectionObserverObserveOptions = {
rootThresholds?: ?$ReadOnlyArray<number>,
};

export type NativeIntersectionObserverToken = mixed;
export opaque type NativeIntersectionObserverToken = mixed;

export interface Spec extends TurboModule {
// TODO(T223605846): Remove legacy observe method
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ import {
} from './internals/RawPerformanceEntry';
import {warnNoNativePerformance} from './internals/Utilities';
import NativePerformance from './specs/NativePerformance';
import nullthrows from 'nullthrows';

export {PerformanceEntry} from './PerformanceEntry';

@@ -130,14 +131,16 @@ export class PerformanceObserver {
this.#nativeObserverHandle = this.#createNativeObserver();
}

const observerHandle = nullthrows(this.#nativeObserverHandle);

if (options.entryTypes) {
this.#type = 'multiple';
NativePerformance.observe?.(this.#nativeObserverHandle, {
NativePerformance.observe?.(observerHandle, {
entryTypes: options.entryTypes.map(performanceEntryTypeToRaw),
});
} else if (options.type) {
this.#type = 'single';
NativePerformance.observe?.(this.#nativeObserverHandle, {
NativePerformance.observe?.(observerHandle, {
type: performanceEntryTypeToRaw(options.type),
buffered: options.buffered,
durationThreshold: options.durationThreshold,
@@ -158,37 +161,38 @@ export class PerformanceObserver {
NativePerformance.disconnect(this.#nativeObserverHandle);
}

#createNativeObserver(): OpaqueNativeObserverHandle {
#createNativeObserver(): OpaqueNativeObserverHandle | null {
if (!NativePerformance || !NativePerformance.createObserver) {
warnNoNativePerformance();
return;
return null;
}

this.#calledAtLeastOnce = false;

return NativePerformance.createObserver(() => {
const rawEntries = NativePerformance.takeRecords?.(
this.#nativeObserverHandle,
true, // sort records
);
if (!rawEntries) {
return;
}

const entries = rawEntries.map(rawToPerformanceEntry);
const entryList = new PerformanceObserverEntryList(entries);

let droppedEntriesCount = 0;
if (!this.#calledAtLeastOnce) {
droppedEntriesCount =
NativePerformance.getDroppedEntriesCount?.(
this.#nativeObserverHandle,
) ?? 0;
this.#calledAtLeastOnce = true;
}

this.#callback(entryList, this, {droppedEntriesCount});
});
const observerHandle: OpaqueNativeObserverHandle =
NativePerformance.createObserver(() => {
const rawEntries = NativePerformance.takeRecords?.(
observerHandle,
true, // sort records
);
if (!rawEntries) {
return;
}

const entries = rawEntries.map(rawToPerformanceEntry);
const entryList = new PerformanceObserverEntryList(entries);

let droppedEntriesCount = 0;
if (!this.#calledAtLeastOnce) {
droppedEntriesCount =
NativePerformance.getDroppedEntriesCount?.(observerHandle) ?? 0;
this.#calledAtLeastOnce = true;
}

this.#callback(entryList, this, {droppedEntriesCount});
});

return observerHandle;
}

#validateObserveOptions(options: PerformanceObserverInit): void {
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ export type RawPerformanceEntry = {
responseStatus?: number,
};

export type OpaqueNativeObserverHandle = mixed;
export opaque type OpaqueNativeObserverHandle = mixed;

export type NativeBatchedObserverCallback = () => void;
export type NativePerformanceMarkResult = number;
Original file line number Diff line number Diff line change
@@ -179,6 +179,7 @@ const NativePerformanceMock = {
createObserver: (
callback: NativeBatchedObserverCallback,
): OpaqueNativeObserverHandle => {
// $FlowExpectedError[incompatible-return]
return createMockObserver(callback);
},

Loading
Oops, something went wrong.