microsoft / microsoft/TypeScript

Incorrect type inference at function callsite - wrapping type inside of a Tuple

オープン
#41,519 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Needs Investigation
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

TypeScript Version: 4.0.5
Tested this on v4.1.0-beta, same issue (in playground)
Tested on Nightly (v4.2.0-dev.20201112), same issue (in playground)
Tested on v3.9.7, same issue (in playground)
Tested on v3.8.3, same issue (in playground)
Tested on v3.7.5, same issue (in playground)

Search Terms:
tuple inference
argument not assignable
ts2345

Code

This is inside of a react typescript project, when upgrading react-scripts along with typescript to the latest (v4) versions.

We have a generic custom react hook that is having some tsc errors after upgrading - this one though I haven't been able to work through and it looks like a bug of some kind in how tsc is inferring the type.

import {
	useReducer,
	useRef,
	useLayoutEffect,
	useMemo,
        Reducer,
} from 'react'; //react: ^16.8.6
import _ from 'lodash'; //lodash: ^4.17.20

export interface IOptions {    
	/**
	 * The number of milliseconds to debounce the request. Defaults to no debounce.
	 * When `true`, defaults to 300 milliseconds.
	 */
	debounce?: boolean | number;
}

export interface ICallbacks<T> {
}

export interface IResult<T> {
	isLoading: boolean;
	response?: T;
	error?: any;
}

export interface IReturn<T> extends IResult<T> {
	refetch(): void;
}

type Action<T> =
	| { type: 'REQUEST' }
	| { type: 'SUCCESS'; payload: T }
	| { type: 'FAILURE'; payload: any };

const initialResults: IResult<any> = {
	isLoading: false,
	response: undefined,
	error: undefined,
};

function resultsReducer<T>(
	state: IResult<T>,
	action: Action<T>,
): IResult<T> {
	switch (action.type) {
		case 'REQUEST':
			return { isLoading: true, response: state.response };
		case 'SUCCESS':
			return { isLoading: false, response: action.payload };
		case 'FAILURE':
			return { isLoading: false, error: action.payload };
		default:
			return initialResults;
	}
}

function isCancellable<T = any>(
	fetch: RequestSender<T>,
): fetch is DebouncedRequestSender<T> {
	if ((fetch as any).cancel) return true;

	return false;
}

type DebouncedRequestSender<T = any> = _.DebouncedFunc<
	(
		url: string,
		options: IOptions,
		callbacks: ICallbacks<T>,
		dispatch: DispatchFunc<Action<T>>,
	) => () => void
>;

type RequestSender<T = any> = DebouncedRequestSender<T> | typeof performRequest;

function performRequest<T = any>(
	url: string,
	options: IOptions,
	callbacks: ICallbacks<T>,
	dispatch: React.Dispatch<Action<T>>,
): (() => void) {
	dispatch({ type: 'REQUEST' });

	return () => {};
};

export function useHook<T = any>(
	url: string,
	options: IOptions = {},
	callbacks: ICallbacks<T> = {},
): void {
	const [results, dispatch] = useReducer<
		Reducer<IResult<T>, Action<T>>
	>(resultsReducer, initialResults);

    // Use a ref to hold onto the callbacks. The prevents us from having to
	// include the callbacks in the useEffect dependencies.
	const callbacksRef = useRef(callbacks);
	callbacksRef.current = callbacks;

    // Create a debounced function, if needed. Also, memoize this function so
	// that it doesn't cause unnecessary requests.
	const fetch: RequestSender<T> = useMemo(() => {
		if (options.debounce) {
			// Use a default number of milliseconds if debounce is `true`.
			const wait = options.debounce === true ? 300 : options.debounce;
			return _.debounce(performRequest, wait);
		} else {
			return performRequest;
		}
	}, [options.debounce]);

    useLayoutEffect(() => {
		const cancelFetch: (() => void) | undefined = fetch(
			url,
			options,
			callbacksRef.current,
			dispatch,
		);

		return () => {
			// Cancel the debounced function call, if needed.
			if (isCancellable(fetch)) fetch.cancel();
		};
	}, [url, options, fetch]);

}  

Expected behavior:
I would expect this not to result in a tsc error. I'm not sure where it is inferring this tuple that is wrapping the actual type for the dispatch variable, but based on what intellisense is showing me in VS Code, the dispatch variable's type matches the expected type for the fetch function signature.

Actual behavior:
Argument of type '[Dispatch<Action<T>>]' is not assignable to parameter of type 'Dispatch<Action<T>>'. Type '[Dispatch<Action<T>>]' provides no match for the signature '(value: Action<T>): void'.ts(2345)

Playground Link:
TS Playground link

Related Issues:

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

リンク先の TypeScript Playground の再現コードから始め、dispatch が fetch に渡されている useReducer の呼び出し箇所を調べます。推論された型を、記載された TypeScript の各バージョンにおける報告済みの tuple-wrapped 診断と比較します。意図されたジェネリック hook の動作を変更せずに、再現コードの型チェックが通れば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
react, typescript
領域
compilers
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。