microsoft / microsoft/TypeScript

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

Đang mở
#41,519 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Needs Investigation
Ngôn ngữ chính
Go
Star
111k
Fork
14.4k
Merge trung bình
1 ngày 19 giờ
Pull request đã merge (30 ngày)
117

Mô tả

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:

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với bản tái hiện TypeScript Playground được liên kết và kiểm tra callsite của useReducer, nơi dispatch được truyền vào fetch. So sánh kiểu được suy luận với chẩn đoán tuple-wrapped được báo cáo trên các phiên bản TypeScript được liệt kê. Công việc được xem là hoàn tất khi bản tái hiện kiểm tra kiểu thành công mà không thay đổi hành vi generic hook dự kiến.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
react, typescript
Lĩnh vực
compilers
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.