2

I have a function which accepts a single object parameter as follows. (Note, this is a contrived example but it demonstrates the problem.)

type FuncParams = { sayYes: boolean; }
type FuncType = (params: FuncParams) => string;

const maybeSayYes: FuncType = ({ sayYes }) => sayYes ? 'Yes' : 'No';

maybeSayYes({ sayYes: true }); // Returns 'Yes'
maybeSayYes({ sayYes: false }); // Returns 'No'

I want to make the sayYes property optional, so I updated the types as follows and added a default to the function declaration:

type FuncParams = { sayYes?: boolean; }
type FuncType = (params: FuncParams) => string;

const maybeSayYes: FuncType = ({ sayYes = true }) => sayYes ? 'Yes' : 'No';

maybeSayYes({ sayYes: true }); // Returns 'Yes'
maybeSayYes({ sayYes: false }); // Returns 'No'
maybeSayYes({}); // Returns 'Yes'

However, I noticed that I can assign any default value to sayYes and TypeScript doesn't flag up the incorrect type. For example:

const maybeSayYes: FuncType = ({ sayYes = 'notABoolean' }) => sayYes ? 'Yes' : 'No';

I expected TypeScript to infer the boolean type from FuncType and stop me assigning a string, but it doesn't.

I can get the error to show if I explicitly type the parameter as follows:

const maybeSayYes: FuncType = ({ sayYes = 'notABoolean' }: FuncParams) => sayYes ? 'Yes' : 'No';

However, I assumed TypeScript would infer this type from FuncType. I'm sure it's my understanding of TypeScript that's at fault, but I'd like to know:

  1. Why TypeScript doesn't tell me that 'notABoolean' isn't a boolean.
  2. How I can constrain the default value type without having to duplicate the FuncParams typing.

Reproduction on TS Playground

2
  • This is weird behavior; not sure if it's been reported before. The behavior is type safe (it's fine for function implementation to widen their parameter types), so it's not exactly wrong, but I don't see a use case for it. Still looking Commented Apr 23, 2021 at 14:41
  • Okay, it's microsoft/TypeScript#19127, this is working as intended. I guess they want a union here because without it, boolean gets messed up (as it's represented as true | false). Commented Apr 23, 2021 at 15:07

1 Answer 1

3

I'm surprised by the behavior you describe, but my knowledge of TypeScript is still not very deep.

You can do it using a function overload type:

type FuncParams = { sayYes?: boolean; }
type FuncType = {
    (params: FuncParams): string;
    (params: FuncParams & {sayYes: never}): string; // Or just: {params: {sayYes: never}: any;
};

Then, a call that tries to use sayYes with a non-boolean type will match the second overload and cause an error. (Many thanks to jcalz for pointing out that sayYes can remain optional, and that you can use a simpler signature for he never overload.)

That gives you this behavior:

// Correct default value type
const maybeSayYes1: FuncType = ({ sayYes = true }) => sayYes ? 'Yes' : 'No';

// Incorrect default value type
const maybeSayYes2: FuncType = ({ sayYes = 'notABoolean' }) => sayYes ? 'Yes' : 'No';
//    ^−−−−−−−−−−−−− TypeScript compiler error here

// Correctly don't have it at all (since it's optional)
const maybeSayYes3: FuncType = ({ }) => Math.random() ? 'Yes' : 'No';

Playground link

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks, that makes sense. Although my minimal example only had one optional property, in reality I need multiple - is there any way to achieve this without a long list of overload types? I was hoping TS would interpret the fact that it's optional and essentially do this automatically.
@petewritescode - I bet it's possible with mapped types and a generic type argument, but I don't immediately know how.
BTW, this behavior, while surprising to me too, is apparently working as intended as per microsoft/TypeScript#19127.
I don't quite get why the overload fixes it; overloads tend to cause other problems, so it's up to OP to decide if this is worth it. Anyway, I think you can keep FuncParams optional and just add the overload mentioning one of the optional properties and it will still work even if there are more optional properties. Like this code. But yeah, this whole thing is a weird one.
@T.J.Crowder Ok, thanks, I'm new here so wasn't sure what the etiquette was :) Thanks again!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.