microsoft / microsoft/TypeScript

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

Abierto
#41,519 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Needs Investigation
Lenguaje dominante
Go
Estrellas
111k
Forks
14.3k
Merge medio
1 d 19 h
PR fusionados (30 d)
117

Descripción

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:

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Comienza con la reproducción vinculada de TypeScript Playground e inspecciona el punto de llamada de useReducer donde dispatch se pasa a fetch. Compara el tipo inferido con el diagnóstico reportado envuelto en una tupla en las versiones de TypeScript indicadas. Se considera terminado cuando la reproducción supera la comprobación de tipos sin cambiar el comportamiento previsto del hook genérico.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
react, typescript
Área
compilers
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.