Skip to content

Narrowing type of an object property with type guard does not allow us to assign/return the instance as having a narrower prop type. #32595

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
IKoshelev opened this issue Jul 28, 2019 · 5 comments

Comments

@IKoshelev
Copy link

IKoshelev commented Jul 28, 2019

TypeScript Version: 3.5.1, 3.5.3

Search Terms: guard type-guard property return assignment

Code

type WideProp = {
  prop: (number | string);
}

type NarrowProp = {
  prop: string;
}

function showcase(): NarrowProp {
  const wide: WideProp = {
    prop: 'aaa' as (string | number)
  };

  const guard = (i: (number | string)): i is string => true;

  if(guard(wide.prop)){
    // this works
    const prop: string = wide.prop;

    // this does not?
    let narrow: { prop: string } = wide;

    // and this does not?
    return wide;
  } 
}

Expected behavior:
Successful type guard check narrowing property prop from string | number to string should allow us to return \ assign containing object instance when a { prop: string } type is expected.

Actual behavior:
With successful type guard check we are allowed to assign property prop when string is expected, but assigning \ returning containing instance as { prop: string } is still forbidden.

Playground Link: https://www.typescriptlang.org/play/#code/C4TwDgpgBA6glgEwgBQE4HsxQLxQN4BQUUYGYAXFABQB2ArgLYBGEqUAPlAM7Cpw0BzAJQBuAgF8CBUJCgA5AIaoMAdzSYc+IiTKUefQWMkEAZnRoBjYHHQ1uAC3QqLCrhCpDKi5U-VZCxBa2PFAqiBCU8Eh+mgHEOpiUAOQKqUlQrtT6-AIcUPTMrELa4mLaQTQhAnRKCJpUcJS0jCxsnNmCQp5QcD1c3Lw5OAB8ULx0EGXEcCZU1bVUYUgAdKSYXXHEAPRbY-Zw-SroqADWXNqBwcAJFAMGubhLEKtkU-E7ewdQCOgQ-TToYAAfguUAANhBrjQlKpKHgbnpBoIoOJNE83ttdgoaNdgPt+j8-vlASD4lBUJC6Kg7OiSlAJEA

Related Issues: #3812

@jack-williams
Copy link
Collaborator

Duplicate of #31755.

@IKoshelev
Copy link
Author

IKoshelev commented Jul 28, 2019

From #31755
"Narrowing the parent would involve synthesizing new types which would be expensive."

This reasoning seems flawed to me. I'm using TS because I need safe code. If expensive compilation was a problem - I'd be using plain JS.

@dragomirtitian
Copy link
Contributor

@IKoshelev There is a balancing act here.. how long are you willing to wait for compilation ? 5s, 10s ? 60s ? How long are you willing to wait for a code completion window to appear before it becomes useless ? 0.5s, 1s? 10s ?

Not adding features that have limited impact but can seriously degrade performance, keeps typescript usable for all of us.

@IKoshelev
Copy link
Author

IKoshelev commented Jul 28, 2019

@dragomirtitian I don't see how this involves intellisense - I'm returning a var and only its name really needs completion. Once I'm done typing return var name - that's when I expect compiler to check whenever its type is suitable. If it warns me about error in type of 'return' 10 seconds later - it is not a problem compared to the alternative of explicitly casting my instance. I mean, with all the time investment into building unit tests and running them before commits, getting a type error 10 seconds later does not seem like a problem to me.

With the --incremental flag this also won't slow down the build AFAIU - whatever check is made, will only be made once per change of this file.

P.S. Maybe you can suggest a type-safe alternative to explicit cast in the code above?

@IKoshelev
Copy link
Author

IKoshelev commented Jul 28, 2019

@dragomirtitian

Never mind, got it

export type NarrowPropTypeByKey<TTarget,
    TKey extends keyof TTarget,
    TNarrow extends TTarget[TKey]> = {
        [key in keyof TTarget]:
        key extends TKey
        /**/ ? TNarrow extends TTarget[key]
        /*  */ ? TNarrow
        /*  */ : TTarget[key]
        /**/ : TTarget[key]
    };

export function narrowPropType<
    T, 
    TKey extends keyof T, 
    TNarrowedPropType extends T[TKey]>
        (inst: T, key: TKey, guard: (wide:  T[TKey]) => wide is TNarrowedPropType): inst is NarrowPropTypeByKey<T, TKey, TNarrowedPropType> {
    
            const val = inst[key];

    if(guard(val)) {
        return true;
    }

    return false;
}

https://www.typescriptlang.org/play/#code/KYDwDg9gTgLgBDAnmYcByBDKUIHcAKOYAKssAEKIDSwiAPMcVgObAwA0AUHD3MTYjigYwAHYATAM5wA1rQgAzPkyisO3XsUzY8QkCInTGLNgG1+tALoA+OAF44Abw29epuYICWo2fKXHVNksALhdXX0FhMSk+ATDXAHoAKiSEuAB+Pm0cXD0DGIC1dyt43mSeVIysrBzSnnK4SuDlExhixEs6uGSmlsC2j07XAF8Abk5OUEhYOAUAV1EAYxhPCB9RGrxCCBIyOjDidjgDgTzo6Q9FPiOD7LxgcW3dlDPDPnMBGy6ACm9JGGahwigIER2Ycyw4ma31wnnEwGa7wsHQAlPZbLD4XBPEY7rgHk9SCgUc0-vAcehNgQiESKNRaAwjsimXiCTSyLZnK4uq5Fmt-nAAG4YAA29mxon+7Us4zCngU33BkO+wpFKLRXPCPCgbDmUB8MCgc2A4xGE1cOpgep8ClFkhNnGGEyQLwA6nDgE9xZqwERoaI5gBbABGwCgcAAPnB-lBvMwUeMnZwXag8V6HD6-dHDXHExN5ksVmtowALPCLDD274kyk6ak7JwaPmS+CYhFwd3w9ON1y+nbNADkGGHA7glbg3xjccjcADIbDKI0Y3NcGbAqVUHE4t+-qDofDUanonjNc82OkR+Y6IQRodGnl3w2dcJZBhHvYA77YAHYIhm-Vmr1GkMAlhSuDQDIkhhGu8Bfs0l7im2AB0X6yq4IpsLOVLNI4cBwdmsbHnAwyIR6aG8Ja1pwG2prESuIE5LOwC5AAojoUDVomQA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants