Skip to content

Object narrawing to function broken on v3.1 #27969

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dxmaxwell opened this issue Oct 18, 2018 · 7 comments
Closed

Object narrawing to function broken on v3.1 #27969

dxmaxwell opened this issue Oct 18, 2018 · 7 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@dxmaxwell
Copy link

TypeScript Version: 3.2.0-dev.20181018

Search Terms: function narrowing guard

Code

// Error in 3.2.0-dev.20181018, but works in v3.0.1!
function stringify(x: {} | (() => string)): string {
    if (typeof x === 'function') {
       return x();
    }
    return String(x);
}

Expected behavior: The example code should compile without error.

Actual behavior: Compiler error: "Cannot invoke an expression whose type lacks a call signature. Type 'Function | (() => string)' has no compatible call signatures."

Playground Link: http://www.typescriptlang.org/play/#src=%2F%2F%20Error%20in%203.2.0-dev.20181018%2C%20but%20works%20in%20v3.0.1!%0D%0Afunction%20stringify(x%3A%20%7B%7D%20%7C%20(()%20%3D%3E%20string))%3A%20string%20%7B%0D%0A%20%20%20%20if%20(typeof%20x%20%3D%3D%3D%20'function')%20%7B%0D%0A%20%20%20%20%20%20%20return%20x()%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%20%20return%20String(x)%3B%0D%0A%7D%0D%0A

Related Issues: None that I could find.

@ghost ghost added Bug A bug in TypeScript and removed Bug A bug in TypeScript labels Oct 18, 2018
@ghost
Copy link

ghost commented Oct 18, 2018

The problem is that all functions are assignable to {}. So {} | (() => string) actually allows any function, not just () => string, so your code as-is allows stringify(() => 2);.
The following should work better:

type FunctionsMustReturnString<T> = T extends Function ? () => string : T;

function stringify<T>(x: FunctionsMustReturnString<T>): string {
    if (typeof x === 'function') {
       return x();
    }
    return String(x);
}

stringify(2); // OK
stringify(() => ""); // OK

stringify(() => 2); // Error
stringify((p: string) => p); // Error

@ghost ghost added the Question An issue which isn't directly actionable in code label Oct 18, 2018
@dxmaxwell
Copy link
Author

Thank you for the response, I will certainly update my code as you have suggested. I guess I should consider this bug in v3.0, rather then a regression in v3.1.

@RyanCavanaugh
Copy link
Member

Also some discussion of a similar pattern in #27278

@jack-williams
Copy link
Collaborator

Related? #26970

@dxmaxwell
Copy link
Author

Something that seems inconsistent is that the following does NOT throw an error in 3.2.0-dev.20181018:

function stringify<T = {} | (() => string)>(x: T): string {
    if (typeof x === 'function') {
       return x();
    }
    return String(x);
}

Which seems equivalent to the original erroneous code?

@ghost
Copy link

ghost commented Oct 18, 2018

When you write T = {} | (() => string) that gives a default value for T, but T doesn't actually have to be that type. It could be null which isn't even assignable to it.
For a type with no known function signatures (e.g. x: unknown), typeof x === "function" narrows x to Function which supports being called with any signature.

@dxmaxwell
Copy link
Author

That makes perfect sense... thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants