This isn't really a bug, although it might be surprising. It's definitely a concatenation issue.
Concatenation at the type level only happens with template literal types, and only sometimes
First: concatenation with + is not performed at the type level. "foo"+"bar" is of type string in TypeScript, not of type "foobar". There was a suggestion a while ago at microsoft/TypeScript#12940 to support concatenation with +, and people ask about it from time to time, but it was never implemented.
Second: concatenation with template literal strings is only sometimes performed at the type level. Depending on where it's used, `${"foo"}${"bar"}` will either be string or "foobar". You can get concatenation at the type level with a const assertion (as implemented in microsoft/TypeScript#40707), or if the template literal string is used in a place that contextually expects the concatenated version (as implemented in microsoft/TypeScript#43376).
So that gives us the following behavior:
const plus = "foo" + "bar" // string
const tl0 = `${"foo"}${"bar"}` // string
const tl1 = `${"foo"}${"bar"}` as const // "foobar"
const tl2: "foobar" = `${"foo"}${"bar"}` // also "foobar"
Excess property checking does not happen with computed keys of type string
TypeScript has structural subtyping, which means that a value v is assignable to a type T if v contains all the members of T. It doesn't matter if v has more members:
function f(x: { a: string }) {
console.log(x.a.toUpperCase());
}
const val = { a: "hello", b: 123 };
f(val); // okay
From the type system's perspective, excess properties are just fine.
But, there's also excess property checking. When you use an object literal directly in a place that expects fewer properties, it's considered a potential bug because you are throwing away information:
f({ a: "hello", b: 123 }); // error!
// -----------> ~~~~~~
// Object literal may only specify known properties
But, excess property checking does not trigger for computed properties of type string. See microsoft/TypeScript#36920; it seems (although nobody has said so explicitly) that this is not a bug, and asking for it to be changed is considered a feature request. (Also see this comment on microsoft/TypeScript#42686). So for now, anyway, we have the following behavior:
f({ a: "hello", ["B".toLowerCase()]: 123 }) // okay
Lack of concatenation at the type level + no excess property checking on string computed keys leads to the behavior you see here. But I can see why the following might be surprising:
const o: Props = {
[`prop${myNumber}`]: true, // works as expected ✔️
[`prop${myString}`]: true, // does not display error ❌
}
The first one doesn't give an error, but for the same reason that the second one doesn't give an error; they are both considered string-valued keys, so neither are considered excess properties. So that first ✔️ is kind of an accident.
If you want the compiler to treat them as template literal types, you can use const assertions:
const p: Props = {
[`prop${myNumber}` as const]: true, // works as expected ✔️
[`prop${myString}` as const]: true, // error as expected ✔️
}
and now both ✔️ are legitimate.
Playground link to code