Skip to content

unknown isn’t narrowed by assignment #43584

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

Open
JonathanDCohen opened this issue Apr 7, 2021 · 7 comments
Open

unknown isn’t narrowed by assignment #43584

JonathanDCohen opened this issue Apr 7, 2021 · 7 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@JonathanDCohen
Copy link

Bug Report

πŸ”Ž Search Terms

try block

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about try-catch blocks

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

function f(request: {params: {[param: string]: unknown;}}) {
    let x = request.params.x;
    try {
        x = validate(x);
    } catch (e) {}

    try {
        takesString(x);  // Fails to compile, x is of type unknown.
    } catch (e) {}
}

function validate(x: unknown): string { return ""; }
function takesString(s: string) {}

πŸ™ Actual behavior

In this example, the code fails to compile because tsc has deduced x in the second block to be of type unknown

πŸ™‚ Expected behavior

Since we have successfully exited the first try-catch block, x's type should be narrowed to string in the second.

@JonathanDCohen
Copy link
Author

JonathanDCohen commented Apr 7, 2021

I've discovered that

function f(request: {params: {[param: string]: unknown;}}) {
    let x = request.params.x;
    try {
        x = validate(x);
        takesString(x)
    } catch (e) {}

also fails to compile. So maybe the issue is related to narrowing the type of a variable in a broader scope?

@andrewbranch andrewbranch changed the title Variable type isn't propagated out of a try block unknown isn’t narrowed by assignment Apr 9, 2021
@andrewbranch
Copy link
Member

I think this is probably a better example of what you’re surprised by, if I’m understanding you.

@JonathanDCohen
Copy link
Author

JonathanDCohen commented Apr 9, 2021 via email

@andrewbranch andrewbranch added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Apr 9, 2021
@emilioplatzer
Copy link

I my opinion you are confused about unknown. If you see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type you can read:

Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.

In your case you can use any to say I support any value. And then all works!

function f(request: {params: {[param: string]: any;}}) {
    let x = request.params.x;
    try {
        x = validate(x);
    } catch (e) {}

    try {
        takesString(x);  // Fails to compile, x is of type unknown.
    } catch (e) {}
}

function validate(x: any): string { return ""; }
function takesString(s: string) {}

Look at Playground

@JonathanDCohen
Copy link
Author

JonathanDCohen commented Apr 22, 2021

We lose type safety with any. In my case the type of params is used for all of our server's endpoint handlers, so using unknown is useful to make sure we don't skip a validation step. I think the true correct way to do this would be to do something like

function validate(x: unknown): x is string {
  // blah blah blah...
}
// ...
function f(request: {params: {[param: string]: unknown;}}) {
    let x = request.params.x;
    if (!validate(x)) { throw SomeError(); }
    // now x is narrowed to `string`
}

which is what I've switched to in my code.

@emilioplatzer
Copy link

I like your point:

using unknown is useful to make sure we don't skip a validation step

I think you are right. Here is anohter example that ilustrates it: Playground

function validate(x: unknown): x is string {
    return typeof x === "string";
}

function mayBeNorS(): number|string{
    if(Math.random()>0.5) return 1;
    return 'x'
}
// ...
function f(request: {params: {[param: string]: unknown;}}) {
    let x = request.params.x;
    if (!validate(x)) { throw Error('some'); }
    // now x is narrowed to `string`
    x = "other";
    // now x is ruined to `unknown`
    x.toLocaleLowerCase();

    // if the original variable was not `unkonwn` it works!
    let y = mayBeNorS();
    y = "other";
    // now y is narrowed to `string`
    y.toLocaleLowerCase();
}

@rChaoz
Copy link

rChaoz commented Sep 28, 2024

Worth noting this also happens with {}, not just unknown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants