Skip to content
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

Narrowing to remove undefined from optional object properties #28882

Closed
ethanresnick opened this issue Dec 6, 2018 · 3 comments
Closed

Narrowing to remove undefined from optional object properties #28882

ethanresnick opened this issue Dec 6, 2018 · 3 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@ethanresnick
Copy link
Contributor

ethanresnick commented Dec 6, 2018

TypeScript Version: 3.2.0-dev.201xxxxx

Search Terms:
narrowing optional required property undefined

Code

interface Message {
  to: string;
  from: string;
}

interface MessageOptionalFrom {
  to: string;
  from?: string;
}

declare function send(data: Message | Message[]): Promise<void>;

function test(x: MessageOptionalFrom) {
    // This doesn't narrow because of 
    // https://github.com/Microsoft/TypeScript/issues/21732
    if (!("from" in x)) {
        throw new Error("");
    }
    
    // But this doesn't narrow either :(
    if (typeof x.from === "undefined") {
        throw new Error("");
    }

    // Or this :(
    if (x.from == null) {
        throw new Error("");
    }

    return send(x);
}

Expected behavior:
One of these approaches to narrowing should work such that, at the bottom of the function, x should be (MessageOptionalFrom & { from: string }), and the call to send should succeed.

Actual behavior:

Argument of type 'MessageOptionalFrom' is not assignable to parameter of type 'Message | Message[]'.
  Type 'MessageOptionalFrom' is not assignable to type 'Message'.
    Property 'from' is optional in type 'MessageOptionalFrom' but required in type 'Message'.

Playground Link:
http://www.typescriptlang.org/play/index.html#src=interface%20Message%20%7B%0D%0A%20%20to%3A%20string%3B%0D%0A%20%20from%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20MessageOptionalFrom%20%7B%0D%0A%20%20to%3A%20string%3B%0D%0A%20%20from%3F%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Adeclare%20function%20send(data%3A%20Message%20%7C%20Message%5B%5D)%3A%20Promise%3Cvoid%3E%3B%0D%0A%0D%0A%2F%2F%20Irl%2C%20this%20function%20would%20set%20the%20default%20%60from%60%2C%20%0D%0A%2F%2F%20or%20error%20if%20that's%20not%20possible%2C%20then%20send.%0D%0A%2F%2F%20For%20now%2C%20it%20always%20errors%20if%20from%20is%20missing.%0D%0Afunction%20test(x%3A%20MessageOptionalFrom)%20%7B%0D%0A%20%20%20%20%2F%2F%20This%20doesn't%20narrow%20because%20of%20%0D%0A%20%20%20%20%2F%2Fhttps%3A%2F%2Fgithub.com%2FMicrosoft%2FTypeScript%2Fissues%2F21732%0D%0A%20%20%20%20if%20(!(%22from%22%20in%20x))%20%7B%0D%0A%20%20%20%20%20%20%20%20throw%20new%20Error(%22%22)%3B%0D%0A%20%20%20%20%7D%0D%0A%20%20%20%20%0D%0A%20%20%20%20%2F%2F%20But%20this%20doesn't%20narrow%20either%20%3A(%0D%0A%20%20%20%20if%20(typeof%20x.from%20%3D%3D%3D%20%22undefined%22)%20%7B%0D%0A%20%20%20%20%20%20%20%20throw%20new%20Error(%22%22)%3B%0D%0A%20%20%20%20%7D%0D%0A%0D%0A%20%20%20%20%2F%2F%20Or%20this%20%3A(%0D%0A%20%20%20%20if%20(x.from%20%3D%3D%20null)%20%7B%0D%0A%20%20%20%20%20%20%20%20throw%20new%20Error(%22%22)%3B%0D%0A%20%20%20%20%7D%0D%0A%0D%0A%20%20%20%20%2F%2F%20x%20should%20be%20(MessageOptionalFrom%20%26%20%7B%20from%3A%20string%20%7D)%20here.%0D%0A%20%20%20%20%2F%2F%20Call%20below%20should%20work%2C%20therefore%3B%20errors%20instead.%0D%0A%20%20%20%20return%20send(x)%3B%0D%0A%7D

Related Issues:
#21732, but only covers one case.

@weswigham weswigham added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Dec 6, 2018
@weswigham
Copy link
Member

Currently when a member of a type is narrowed, it is not reflected in the narrowed type of the containing type.

@Laubeee
Copy link

Laubeee commented Dec 12, 2019

I have one more example

class A {
  prop?: number;
}

function test(a: A) {
  a.prop = 0;
  let prop2: number | undefined = 0;
  (async() =>  {
    a.prop += 1;
    prop2 += 1;
  })();
}

prop2 is correctly narrowed, but a.prop throws object possibly undefined HOWEVER the optional property is correctly narrowed if I remove the async keyword.

@ethanresnick
Copy link
Contributor Author

Realized that the root issue here is the same as #10065 and its various duplicates. Would be really nice to see this addressed. Since #10065 was closed and locked, I guess this is a decent place to track...

@Laubeee Your issue looks like something different (possibly #30625)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants