microsoft / microsoft/TypeScript
Type Guards and CallBack Parameters are any, if worked, could successfully defined a callback type and respective guard
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
TypeScript Version: typescript@3.2.0-dev.20181003
Two bugs in one, if both of these issues work, I would be able to properly defined a callback type function call signature overloads and a type guard to go with it.
CallBack, Type Guards with extends fails, CallBack with overloads signatures types become any type, instead of union of their respective params.
Issues
- Callback types params with methods overloading are always any type, should be respective union of each parameters types.
- Type Guards for more complex type guards doesn't work.
Summary
If all of this were to work, I would be able to correct write a typescript type for a callback with its associated type guard.
1. CallbackTypes are not correctly computed, which expected to be a union of the respective overloads params.
// function call signatures.
interface CallBack<T>
{
() : void
(error : Error) : void
(error : undefined, result : T) : void
(error : null, result : T) : void
}
function takeCallBack(callback: CallBack<number>)
{
callback();
callback(null, {A:77});
callback(new Error(''));
}
//Fails to determine which function signature was used to call, because it could have been any of the //formats. The final format is an any type. It would be nice to be able to write a type guard
//[ts] 'result' is declared but its value is never read.
//[ts] Argument of type '(error: any, result: any) => void' is not assignable to parameter of type 'CallBack<number>'
takeCallBack((error, result) => {
});
Expected, however, this is not enough information us to write a proper type guards
and we have also lost information about the results, which is the most important.
we need to be able to distinguish between T and T | undefined.
takeCallBack((error : Error | undefined | null, result : T | undefined) => {
});
CallBack type formulation
The only was to be able to do this, without having complicate solution, by means on inspection the body of the the function in which the callback is call to determine or infer the variants. Would be to break the callback definition into three.
Callback A:
interface CallBackError<T>
{
() : void // sucessfully, results undefined
(error : Error) : void // Error
}
function takeCallBackNoResults(callback: CallBackError<number>)
{
callback();
callback(new Error(''));
}
//[ts] 'results' is declared but its value is never read.
//[ts] Argument of type '(error: any, results: any) => void' is not assignable to parameter of type 'CallBackError<number>'.
takeCallBackNoResults((error,results) => {
});
takeCallBackNoResults((error : Error,results : undefined) => {
});
I can now write a guard statement for this, to ensure a clean output of results, however, in this case I shouldn't need it.
CallBack B:
Here I can use the null permutation of error, to determine
interface CallBackResult<T>
{
(error : null, res : T) : void
(error : Error) : void
}
function takeCallBackResults(callback: CallBackResult<number>)
{
callback(null, 6);
callback(new Error(''));
}
//[ts] 'results' is declared but its value is never read.
//[ts] Argument of type '(error: any, results: any) => void' is not assignable to parameter of type 'CallBackResult<number>'.
takeCallBackResults((error,results) => {
});
takeCallBackResults((error : Error | null,results : number | undefined) => {
});
I can't write a guard for this, but would be nice if I didn't need it in the first place, but there is now getting around that.
Callback C:
interface CallBackOpResult<T>
{
() : void
(error : null, res : T) : void
(error : Error) : void
}
function takeCallBackOpResults(callback: CallBackOpResults<number>)
{
callback();
callback(null, 5);
callback(new Error(''));
}
//[ts] 'results' is declared but its value is never read.
//[ts] Argument of type '(error: any, results: any) => void' is not assignable to parameter of type 'CallBackError<number>'.
takeCallBackOpResults((error,results) => {
});
takeCallBackNoResults((error : Error | null | undefined,results : number | undefined) => {
});
I can't write a guard for this, but would be nice if I didn't need it in the first place, but there is now getting around that. Because we have to now do an unnecessary check for undefined, when results mayn't have support undefined in the first place.
CallBack - Conclusion on how to write the final form
What we are not able to distinguish here is that, is whether results would be a plain T or meant to tbe T | undefined. This is a problem, the only way we able to to really know that is if error and results parameters could be treated as one and a set. I can write a type gaud, have knowledge of this already, to check for the 3 cases, but I will always have to check for undefined
What I could do is use the fact that error being of type null, means that results shouldn't have the undefined, I could strip it, but I wouldn't be able know difference of which results still
If I decided to not allow undefined error, for default case of no results I could be able to write a gaud check that would work, based on the input of error being null or not.
interface CallBackOpResult<T>
{
() // error = undefined, results = undefined
(error : null, res : T) : void
(error : Error) : void
}
function takeCallBackOPResults(callback: CallBackOpResults<number>)
{
callback();
callback(null, 5);
callback(new Error(''));
}
//[ts] 'results' is declared but its value is never read.
//[ts] Argument of type '(error: any, results: any) => void' is not assignable to parameter of type 'CallBackOpResults<number>'.
takeCallBackOpResults((error,results) => {
});
Simple Use of Type Guard:
takeCallBackNoResults((error : Error | null, results : number) => {
if(!CallBackHasResults(error, results))
console.log(error);// handle error
else
{
results
}
});
Type Guards Failed:
function CallBackHasResults1<Err extends undefined | null | Error, Result>(error : Err, results : Result) : results is undefined | Result
function CallBackHasResults1<Err extends null | Error, Result>(error : Err, results : Result) : results is Result
{
return error !== null && error !== undefined;
//return results;
}
// Fails to work correctly:
const callBack = (error : Error | null | undefined, results : number |undefined) => {
if(!CallBackHasResults1(error, results))
console.log(error);// handle error
else
{
results // expect type = number | undefined
}
}
// Fails to work correctly.
const callBack2 = (error : Error | null, results : number | undefined) => {
if(!CallBackHasResults1(error, results))
console.log(error);// handle error
else
{
results // expect type = number
}
}
The alternative would be to introduce a compound type for callbacks, were by both error and Results are used to dictate the output format, which I have achieved here by doing this manual.
Another alternative, using the this parameter, which may work if the overloads function parameters unioned correctly.
interface CallBackOpResult2<T>
{
() // error = undefined, results = undefined
(error : null, res : T) : void
(error : Error) : void
}
interface TT<T> {
callback : CallBackOpResult2<T>
}
function takeCallBackResults3(callback: TT<number>['callback'])
{
//this = {a : 'a'}
callback();
callback(null, 5);
callback(new Error(''));
}
//[ts] 'results' is declared but its value is never read.
//[ts] Argument of type '(error: any, results: any) => void' is not assignable to parameter of type 'CallBackError<number>'.
takeCallBackResults3((error,results) => {
});
takeCallBackNoResults3(function (error, results) {
if(!CallBackHasResults3(error, results))
console.log(error);// handle error
else
{
results
}
});
function CallBackHasResults3<T extends {callback:any},Err extends undefined | null | Error, Result>(this : T, error : Err, results : Result) : results is undefined | Result
//function CallBackHasResults3<Err extends null | Error, Result>(this : {a:'a'}, error : Err, results : Result) : results is Result
{
return error !== null && error !== undefined;
//return results;
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the callback overload and generic type-guard examples in the issue using the reported TypeScript 3.2.0-dev.20181003 behavior. Reduce them to separate reproductions for overload parameter inference and type-guard narrowing; done means the examples no longer infer callback parameters as any and the stated result types narrow as expected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100