From 28d97eadacd5aff037ac934e29b311564ffd34bb Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Fri, 23 Sep 2022 10:28:34 -0400 Subject: [PATCH] Flow: typing of Scheduler (#25317) Enables well formed exports for /scheduler. Some of the modules there were missing `@flow` and were therefore completely unchecked (despite some spurious types sprinkled around). --- .../scheduler/src/SchedulerFeatureFlags.js | 1 + packages/scheduler/src/SchedulerMinHeap.js | 15 ++-- packages/scheduler/src/SchedulerPriorities.js | 2 +- packages/scheduler/src/forks/Scheduler.js | 68 +++++++++++++++---- .../SchedulerFeatureFlags.www-dynamic.js | 1 - .../src/forks/SchedulerFeatureFlags.www.js | 5 +- packages/scheduler/src/forks/SchedulerMock.js | 67 +++++++++++++----- .../scheduler/src/forks/SchedulerPostTask.js | 6 +- scripts/flow/config/flowconfig | 1 + 9 files changed, 120 insertions(+), 46 deletions(-) diff --git a/packages/scheduler/src/SchedulerFeatureFlags.js b/packages/scheduler/src/SchedulerFeatureFlags.js index 8bcdfa4498efa..e9188ea6df504 100644 --- a/packages/scheduler/src/SchedulerFeatureFlags.js +++ b/packages/scheduler/src/SchedulerFeatureFlags.js @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow strict */ export const enableSchedulerDebugging = false; diff --git a/packages/scheduler/src/SchedulerMinHeap.js b/packages/scheduler/src/SchedulerMinHeap.js index b8585e10358f4..805be28ef90f8 100644 --- a/packages/scheduler/src/SchedulerMinHeap.js +++ b/packages/scheduler/src/SchedulerMinHeap.js @@ -7,23 +7,24 @@ * @flow strict */ -type Heap = Array; +type Heap = Array; type Node = { id: number, sortIndex: number, + ... }; -export function push(heap: Heap, node: Node): void { +export function push(heap: Heap, node: T): void { const index = heap.length; heap.push(node); siftUp(heap, node, index); } -export function peek(heap: Heap): Node | null { +export function peek(heap: Heap): T | null { return heap.length === 0 ? null : heap[0]; } -export function pop(heap: Heap): Node | null { +export function pop(heap: Heap): T | null { if (heap.length === 0) { return null; } @@ -36,7 +37,7 @@ export function pop(heap: Heap): Node | null { return first; } -function siftUp(heap, node, i) { +function siftUp(heap: Heap, node: T, i: number): void { let index = i; while (index > 0) { const parentIndex = (index - 1) >>> 1; @@ -53,7 +54,7 @@ function siftUp(heap, node, i) { } } -function siftDown(heap, node, i) { +function siftDown(heap: Heap, node: T, i: number): void { let index = i; const length = heap.length; const halfLength = length >>> 1; @@ -85,7 +86,7 @@ function siftDown(heap, node, i) { } } -function compare(a, b) { +function compare(a: Node, b: Node) { // Compare sort index first, then task id. const diff = a.sortIndex - b.sortIndex; return diff !== 0 ? diff : a.id - b.id; diff --git a/packages/scheduler/src/SchedulerPriorities.js b/packages/scheduler/src/SchedulerPriorities.js index 1d46ae0e48cd7..d466a64fd979e 100644 --- a/packages/scheduler/src/SchedulerPriorities.js +++ b/packages/scheduler/src/SchedulerPriorities.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict */ export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5; diff --git a/packages/scheduler/src/forks/Scheduler.js b/packages/scheduler/src/forks/Scheduler.js index 3350c798fabe1..fedc4510f7420 100644 --- a/packages/scheduler/src/forks/Scheduler.js +++ b/packages/scheduler/src/forks/Scheduler.js @@ -4,10 +4,13 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow */ /* eslint-disable no-var */ +import type {PriorityLevel} from '../SchedulerPriorities'; + import { enableSchedulerDebugging, enableProfiling, @@ -41,7 +44,19 @@ import { startLoggingProfilingEvents, } from '../SchedulerProfiling'; -let getCurrentTime; +export type Callback = boolean => ?Callback; + +type Task = { + id: number, + callback: Callback | null, + priorityLevel: PriorityLevel, + startTime: number, + expirationTime: number, + sortIndex: number, + isQueued?: boolean, +}; + +let getCurrentTime: () => number | DOMHighResTimeStamp; const hasPerformanceNow = typeof performance === 'object' && typeof performance.now === 'function'; @@ -96,7 +111,9 @@ const localSetImmediate = const isInputPending = typeof navigator !== 'undefined' && + // $FlowFixMe[prop-missing] navigator.scheduling !== undefined && + // $FlowFixMe[incompatible-type] navigator.scheduling.isInputPending !== undefined ? navigator.scheduling.isInputPending.bind(navigator.scheduling) : null; @@ -247,7 +264,10 @@ function workLoop(hasTimeRemaining, initialTime) { } } -function unstable_runWithPriority(priorityLevel, eventHandler) { +function unstable_runWithPriority( + priorityLevel: PriorityLevel, + eventHandler: () => T, +): T { switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: @@ -269,7 +289,7 @@ function unstable_runWithPriority(priorityLevel, eventHandler) { } } -function unstable_next(eventHandler) { +function unstable_next(eventHandler: () => T): T { var priorityLevel; switch (currentPriorityLevel) { case ImmediatePriority: @@ -294,8 +314,9 @@ function unstable_next(eventHandler) { } } -function unstable_wrapCallback(callback) { +function unstable_wrapCallback) => mixed>(callback: T): T { var parentPriorityLevel = currentPriorityLevel; + // $FlowFixMe[incompatible-return] return function() { // This is a fork of runWithPriority, inlined for performance. var previousPriorityLevel = currentPriorityLevel; @@ -309,7 +330,11 @@ function unstable_wrapCallback(callback) { }; } -function unstable_scheduleCallback(priorityLevel, callback, options) { +function unstable_scheduleCallback( + priorityLevel: PriorityLevel, + callback: Callback, + options?: {delay: number}, +): Task { var currentTime = getCurrentTime(); var startTime; @@ -346,7 +371,7 @@ function unstable_scheduleCallback(priorityLevel, callback, options) { var expirationTime = startTime + timeout; - var newTask = { + var newTask: Task = { id: taskIdCounter++, callback, priorityLevel, @@ -403,11 +428,11 @@ function unstable_continueExecution() { } } -function unstable_getFirstCallbackNode() { +function unstable_getFirstCallbackNode(): Task | null { return peek(taskQueue); } -function unstable_cancelCallback(task) { +function unstable_cancelCallback(task: Task) { if (enableProfiling) { if (task.isQueued) { const currentTime = getCurrentTime(); @@ -422,13 +447,18 @@ function unstable_cancelCallback(task) { task.callback = null; } -function unstable_getCurrentPriorityLevel() { +function unstable_getCurrentPriorityLevel(): PriorityLevel { return currentPriorityLevel; } let isMessageLoopRunning = false; -let scheduledHostCallback = null; -let taskTimeoutID = -1; +let scheduledHostCallback: + | null + | (( + hasTimeRemaining: boolean, + initialTime: DOMHighResTimeStamp | number, + ) => boolean) = null; +let taskTimeoutID: TimeoutID = (-1: any); // Scheduler periodically yields in case there is other work on the main // thread, like user events. By default, it yields multiple times per frame. @@ -441,7 +471,7 @@ let startTime = -1; let needsPaint = false; -function shouldYieldToHost() { +function shouldYieldToHost(): boolean { const timeElapsed = getCurrentTime() - startTime; if (timeElapsed < frameInterval) { // The main thread has only been blocked for a really short amount of time; @@ -490,7 +520,9 @@ function requestPaint() { if ( enableIsInputPending && navigator !== undefined && + // $FlowFixMe[prop-missing] navigator.scheduling !== undefined && + // $FlowFixMe[incompatible-type] navigator.scheduling.isInputPending !== undefined ) { needsPaint = true; @@ -499,7 +531,7 @@ function requestPaint() { // Since we yield every frame regardless, `requestPaint` has no effect. } -function forceFrameRate(fps) { +function forceFrameRate(fps: number) { if (fps < 0 || fps > 125) { // Using console['error'] to evade Babel and ESLint console['error']( @@ -579,6 +611,7 @@ if (typeof localSetImmediate === 'function') { } else { // We should only fallback here in non-browser environments. schedulePerformWorkUntilDeadline = () => { + // $FlowFixMe[not-a-function] nullable value localSetTimeout(performWorkUntilDeadline, 0); }; } @@ -592,14 +625,16 @@ function requestHostCallback(callback) { } function requestHostTimeout(callback, ms) { + // $FlowFixMe[not-a-function] nullable value taskTimeoutID = localSetTimeout(() => { callback(getCurrentTime()); }, ms); } function cancelHostTimeout() { + // $FlowFixMe[not-a-function] nullable value localClearTimeout(taskTimeoutID); - taskTimeoutID = -1; + taskTimeoutID = ((-1: any): TimeoutID); } export { @@ -623,7 +658,10 @@ export { forceFrameRate as unstable_forceFrameRate, }; -export const unstable_Profiling = enableProfiling +export const unstable_Profiling: { + startLoggingProfilingEvents(): void, + stopLoggingProfilingEvents(): ArrayBuffer | null, +} | null = enableProfiling ? { startLoggingProfilingEvents, stopLoggingProfilingEvents, diff --git a/packages/scheduler/src/forks/SchedulerFeatureFlags.www-dynamic.js b/packages/scheduler/src/forks/SchedulerFeatureFlags.www-dynamic.js index 30a94e4d78f3a..e6f0155843a0a 100644 --- a/packages/scheduler/src/forks/SchedulerFeatureFlags.www-dynamic.js +++ b/packages/scheduler/src/forks/SchedulerFeatureFlags.www-dynamic.js @@ -3,7 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ // In www, these flags are controlled by GKs. Because most GKs have some diff --git a/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js b/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js index e3731d3c566ba..2e52afae90b0b 100644 --- a/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js +++ b/packages/scheduler/src/forks/SchedulerFeatureFlags.www.js @@ -4,8 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow */ +// $FlowFixMe[cannot-resolve-module] const dynamicFeatureFlags = require('SchedulerFeatureFlags'); // Re-export dynamic flags from the www version. @@ -19,4 +21,5 @@ export const { maxYieldMs, } = dynamicFeatureFlags; -export const enableProfiling = __PROFILE__ && enableProfilingFeatureFlag; +export const enableProfiling: boolean = + __PROFILE__ && enableProfilingFeatureFlag; diff --git a/packages/scheduler/src/forks/SchedulerMock.js b/packages/scheduler/src/forks/SchedulerMock.js index 68dac0a7e6876..d7402dcb19621 100644 --- a/packages/scheduler/src/forks/SchedulerMock.js +++ b/packages/scheduler/src/forks/SchedulerMock.js @@ -4,11 +4,14 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow */ /* eslint-disable no-var */ /* eslint-disable react-internal/prod-error-codes */ +import type {PriorityLevel} from '../SchedulerPriorities'; + import { enableSchedulerDebugging, enableProfiling, @@ -36,6 +39,18 @@ import { startLoggingProfilingEvents, } from '../SchedulerProfiling'; +type Callback = boolean => ?Callback; + +type Task = { + id: number, + callback: Callback | null, + priorityLevel: PriorityLevel, + startTime: number, + expirationTime: number, + sortIndex: number, + isQueued?: boolean, +}; + // Max 31 bit integer. The max integer size in V8 for 32-bit systems. // Math.pow(2, 30) - 1 // 0b111111111111111111111111111111 @@ -52,7 +67,7 @@ var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Tasks are stored on a min heap var taskQueue = []; -var timerQueue = []; +var timerQueue: Array = []; // Incrementing id counter. Used to maintain insertion order. var taskIdCounter = 1; @@ -70,7 +85,12 @@ var isHostCallbackScheduled = false; var isHostTimeoutScheduled = false; let currentMockTime: number = 0; -let scheduledCallback: ((boolean, number) => void) | null = null; +let scheduledCallback: + | null + | (( + hasTimeRemaining: boolean, + initialTime: DOMHighResTimeStamp | number, + ) => boolean) = null; let scheduledTimeout: (number => void) | null = null; let timeoutTime: number = -1; let yieldedValues: Array | null = null; @@ -82,11 +102,11 @@ let shouldYieldForPaint: boolean = false; var disableYieldValue = false; -function setDisableYieldValue(newValue) { +function setDisableYieldValue(newValue: boolean) { disableYieldValue = newValue; } -function advanceTimers(currentTime) { +function advanceTimers(currentTime: number) { // Check for tasks that are no longer delayed and add them to the queue. let timer = peek(timerQueue); while (timer !== null) { @@ -110,7 +130,7 @@ function advanceTimers(currentTime) { } } -function handleTimeout(currentTime) { +function handleTimeout(currentTime: number) { isHostTimeoutScheduled = false; advanceTimers(currentTime); @@ -127,7 +147,7 @@ function handleTimeout(currentTime) { } } -function flushWork(hasTimeRemaining, initialTime) { +function flushWork(hasTimeRemaining: boolean, initialTime: number) { if (enableProfiling) { markSchedulerUnsuspended(initialTime); } @@ -169,7 +189,7 @@ function flushWork(hasTimeRemaining, initialTime) { } } -function workLoop(hasTimeRemaining, initialTime) { +function workLoop(hasTimeRemaining, initialTime: number): boolean { let currentTime = initialTime; advanceTimers(currentTime); currentTask = peek(taskQueue); @@ -238,7 +258,10 @@ function workLoop(hasTimeRemaining, initialTime) { } } -function unstable_runWithPriority(priorityLevel, eventHandler) { +function unstable_runWithPriority( + priorityLevel: PriorityLevel, + eventHandler: () => T, +): T { switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: @@ -260,7 +283,7 @@ function unstable_runWithPriority(priorityLevel, eventHandler) { } } -function unstable_next(eventHandler) { +function unstable_next(eventHandler: () => T): T { var priorityLevel; switch (currentPriorityLevel) { case ImmediatePriority: @@ -285,8 +308,9 @@ function unstable_next(eventHandler) { } } -function unstable_wrapCallback(callback) { +function unstable_wrapCallback) => mixed>(callback: T): T { var parentPriorityLevel = currentPriorityLevel; + // $FlowFixMe[incompatible-return] return function() { // This is a fork of runWithPriority, inlined for performance. var previousPriorityLevel = currentPriorityLevel; @@ -300,7 +324,11 @@ function unstable_wrapCallback(callback) { }; } -function unstable_scheduleCallback(priorityLevel, callback, options) { +function unstable_scheduleCallback( + priorityLevel: PriorityLevel, + callback: Callback, + options?: {delay: number}, +): Task { var currentTime = getCurrentTime(); var startTime; @@ -337,7 +365,7 @@ function unstable_scheduleCallback(priorityLevel, callback, options) { var expirationTime = startTime + timeout; - var newTask = { + var newTask: Task = { id: taskIdCounter++, callback, priorityLevel, @@ -394,11 +422,11 @@ function unstable_continueExecution() { } } -function unstable_getFirstCallbackNode() { +function unstable_getFirstCallbackNode(): Task | null { return peek(taskQueue); } -function unstable_cancelCallback(task) { +function unstable_cancelCallback(task: Task) { if (enableProfiling) { if (task.isQueued) { const currentTime = getCurrentTime(); @@ -413,11 +441,11 @@ function unstable_cancelCallback(task) { task.callback = null; } -function unstable_getCurrentPriorityLevel() { +function unstable_getCurrentPriorityLevel(): PriorityLevel { return currentPriorityLevel; } -function requestHostCallback(callback: boolean => void) { +function requestHostCallback(callback: (boolean, number) => boolean) { scheduledCallback = callback; } @@ -494,7 +522,7 @@ function unstable_flushNumberOfYields(count: number): void { } } -function unstable_flushUntilNextPaint(): void { +function unstable_flushUntilNextPaint(): false { if (isFlushing) { throw new Error('Already flushing work.'); } @@ -657,7 +685,10 @@ export { setDisableYieldValue as unstable_setDisableYieldValue, }; -export const unstable_Profiling = enableProfiling +export const unstable_Profiling: { + startLoggingProfilingEvents(): void, + stopLoggingProfilingEvents(): ArrayBuffer | null, +} | null = enableProfiling ? { startLoggingProfilingEvents, stopLoggingProfilingEvents, diff --git a/packages/scheduler/src/forks/SchedulerPostTask.js b/packages/scheduler/src/forks/SchedulerPostTask.js index 36074d5a97c39..dfbfaa8c4df80 100644 --- a/packages/scheduler/src/forks/SchedulerPostTask.js +++ b/packages/scheduler/src/forks/SchedulerPostTask.js @@ -44,7 +44,7 @@ const setTimeout = window.setTimeout; // Use experimental Chrome Scheduler postTask API. const scheduler = global.scheduler; -const getCurrentTime = perf.now.bind(perf); +const getCurrentTime: () => DOMHighResTimeStamp = perf.now.bind(perf); export const unstable_now = getCurrentTime; @@ -193,7 +193,7 @@ export function unstable_runWithPriority( } } -export function unstable_getCurrentPriorityLevel() { +export function unstable_getCurrentPriorityLevel(): PriorityLevel { return currentPriorityLevel_DEPRECATED; } @@ -240,7 +240,7 @@ export function unstable_pauseExecution() {} export function unstable_continueExecution() {} -export function unstable_getFirstCallbackNode() { +export function unstable_getFirstCallbackNode(): null { return null; } diff --git a/scripts/flow/config/flowconfig b/scripts/flow/config/flowconfig index 5c241ef735ce9..bdd313dbd5dd9 100644 --- a/scripts/flow/config/flowconfig +++ b/scripts/flow/config/flowconfig @@ -51,6 +51,7 @@ well_formed_exports=true well_formed_exports.includes=/packages/react-devtools-shared well_formed_exports.includes=/packages/react-devtools-shell well_formed_exports.includes=/packages/react-devtools-timeline +well_formed_exports.includes=/packages/scheduler # Substituted by createFlowConfig.js: %REACT_RENDERER_FLOW_OPTIONS%