1

I am rather new to typescript and are trying to assign the correct types to my JS files. While doing so, I am trying to write as view boilerplate as possible and so I stumbled over this:

I receive compilation errors only under specific conditions. As soon as I call the array filter method after line 14, it starts failing to compile but doesn't fail at line 17.

Why does it fail when calling filter and why does it not fail in line 17?

interface A {
  iAm: 'a';
}
  
interface B {
  iAm: 'b';
}

interface C {
  iAm: string;
}


const fails: (A|B)[] = [{iAm: 'a'}, {iAm: 'b'}]
  .filter(() => true);

const works: (B|C)[] = [{iAm: 'b'}, {iAm: 'c'}]
  .filter(() => true);

Fiddle

Error:

const fails: (A | B)[]
Type '{ iAm: string; }[]' is not assignable to type '(A | B)[]'.
  Type '{ iAm: string; }' is not assignable to type 'A | B'.
    Type '{ iAm: string; }' is not assignable to type 'B'.
      Types of property 'iAm' are incompatible.
        Type 'string' is not assignable to type '"b"'.(2322)
3
  • Compiler heuristics tend to widen string literals to string unless there is a local hint to do otherwise; I'd suggest using a const assertion as shown here. Does that address the question fully? If so I'll write up an answer explaining; otherwise, what am I missing? Commented Jan 31, 2023 at 21:08
  • thank you, I think Logan had a similar Idea. I adjusted my question to your suggestions thanks for the advice! I used your solution to specify my problem a little further since was trying to reference the values inside the filter with no luck. Please have a look into [this version](www.tinyurl.com/4m3pem4k), where I was able to solve it in an acceptable way with your advice I think. I will dive deeper into the const topic but I guess that's the answer. Commented Jan 31, 2023 at 21:36
  • damn, the url is to long, but you should be able to copy past the tiny URL for now to understand the issue Commented Jan 31, 2023 at 21:38

1 Answer 1

2

This is because typescript is inferring {iAm: 'a'} as type {iAm: string}.

To tell typescript that you mean the literal string 'a' you can use "as const".

Your code example will become.

interface A {
  iAm: 'a';
}
  
interface B {
  iAm: 'b';
}

interface C {
  iAm: string;
}


const fails: (A|B)[] = [{iAm: 'a'}, {iAm: 'b'}]
  .filter((x) => {
    return true
  });

const failsButNowWorks: (A|B)[] = [{iAm: 'a' as const}, {iAm: 'b' as const}]
  .filter((x) => {
    return true
  });


const works: (B|C)[] = [{iAm: 'b'}, {iAm: 'c'}]
  .filter(() => true);

I believe it is the .filter that is causing this conversion since you can technically modify the value in a .filter

For example

const works: (B|C)[] = [{iAm: 'b'}, {iAm: 'c'}]
  .filter((x) => {
    x.iAm = 'asdf'
    return true
  });

But if you use "as const" it will infer it correctly and cause a type error.

const failsButNowWorks: (A|B)[] = [{iAm: 'a' as const}, {iAm: 'b' as const}]
  .filter((x) => {
// Type Error here since iAm now has type `{iAm: 'a' | 'b' }`
    x.iAm = 'asdf'
    return true
  });
Sign up to request clarification or add additional context in comments.

Comments

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.