Skip to content

Default parameter narrowed to be defined when using void | undefined | SomeType to type the function parameter  #48664

Closed as not planned
@dummdidumm

Description

@dummdidumm

Bug Report

When typing a function parameter so that it's optional by typing it as void | SomeType method and defining a default parameter, the type is not narrowed to SomeType and instead stays as void | SomeType. TS can be tricked into doing so by writing void | undefined | SomeType, but this feels inconsistent.

From what I've read, void means "I promise I won't use this return value" in the context of fnThatAccecptsVoidFn in the code snippet below, so the return value can actually be anything, which means a default parameter in fnWithDefaultParam1/2 maybe cannot be used to narrow the function parameter to SomeType.

This is either ok because wo broke our promise of not using the return value of the function in fnThatAccecptsVoidFn - in this case void | SomeType with a default value should also be narrowed to SomeType. Or TS plays it safe and says "if void is accepted, anything could be passed in" and not narrow void | undefined | SomeType to SomeType.

Before you say "you could just make this an optional function parameter using ?": This is the minimum reproducible, the actual code is in the context of sveltejs/svelte#7442 where we try to type out a function interface that makes it possible to either set a parameter as required, optional, or missing, where I found this (in my mind) inconsistency. This is related to #12400.

🔎 Search Terms

function default parameter void undefined

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about void, function default parameter

⏯ Playground Link

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAZmA6gSygCxAVygEQKZwCGmANlAAqEBOhAtjALwwAUADtXQFwwBuIyAExgAfGAG9C3KFUx4AvgEpGAPnEAoGJpjsatAHSEA3DAD0J+IWQkIAGhh4AHqzzAoeAWrlq1oSLAQo6PhEpBQctACMjCw6XLz8QqISUjLy0WIwkjDSsjCKKupa2uEGxmYWVrYwmGCOzq7uAPye3r7Q8EioaMHEZJS6AEzRbOHcfIIi1WACBMi1ieJZOWlMGUupeUoMqmIaWrH6RqbmAO4gVADWVXUubgLNXj7g7QgAKmiEUACCwMAurFAIAA1BIAMTAwwQ3GYW1U4wEsMKWgCXR6oX6dAGzAQMIUZXMVDwUEwVAhBxgwEIEIARngYNA0PZrHT0FTxJkUrk5DBzlMZnA5u4Wmo3h9vr9-oCQYJwcwYQVlni1EA

💻 Code

const fnWithoutDefaultParam = (param: void | {a: true}) => {
    param.a; // fails, expected
}

const fnWithDefaultParam1 = (param: void | {a: true} = { a: true }) => {
    param.a; // fails, unexpected?
}

const fnWithDefaultParam2 = (param: void | undefined | {a: true} = { a: true }) => {
    param.a; // works, expected?
}

const fnThatAccecptsVoidFn = (fn: () => void) => {
    fnWithDefaultParam2(fn()); // return param can be sth else than { a: true } or undefined. Type-hole that is ok because we break our promise of not using the return type?
}

fnThatAccecptsVoidFn(() => true);

🙁 Actual behavior

void | SomeType + a default parameter don't narrow it to SomeType, but void | undefined | SomeType do, which feels inconsistent. See first section for more details.

🙂 Expected behavior

Honestly I don't know, see sections above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions