microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.93k stars 12.47k forks source link

Error in Index Signature Assignment #58442

Open foxcaulfield opened 5 months ago

foxcaulfield commented 5 months ago

🔎 Search Terms

"index signature error", "numeric index incompatible with interface", "index signature assignment error", "object literal known properties error", "interface index signature bug", "interface property compatibility error"

🕗 Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.4.5#code/PQKgBAxghgzgpmAjGEwBQBLAdgFzgJwDMoIEBJAFQHsAHCgTxoQG80wwcBpOegLjBg582AOYBuNAF80mXAWKkwZAMoBXAEYMmYOAA88WACYwl1OoxZsB3PgKGiJ02XiIlyFOILJyFl9gG0aAEF+QWEsEQBdfkpaLTgJAJoAIX4sVQBbdQJopTVNC0cZCCosQQ5PHAB5dQArGI8vHzcwAF4wVnYofmYOG34AIhwANSgAGwGwSQAaK0Qevp5BkfGB6eslsAGYUYmptGBgdmOT07OAPTAACwI4AHITHBudfHwqfCkJA6OAWj+wP4-AH-GSgSCwBAAJhQ6GwLl8pjiFgA+tDOot6KjQvYIkVnPIWioNPFUTp9HAjCZYuYmKT0TAbFi7OFxFIZHCCYpKJVvHhfHSrIEgkywqJctSSZDEmBAskmQADGAaZEAEmYooiknl4vykrxJTKOAqghqtSZ3KafLcpPa6O6HQxTKGu1JkxmViV6mR6gWXB4TpWY1d6wZ-shgx241RbqshzO8YT8cuN3w90ezwIbw+kjEQA

💻 Code

/* case 1 */
interface ITopType {
  tKey: string;
}

interface ISubType extends ITopType {
  sKey: string;
}

interface ITestInteface {
  [pA: string]: ITopType;
  [pB: number]: ISubType;
}

const testObj: ITestInteface = {
  a: { tKey: "tVal" },
  1: { tKey: "tVal", sKey: "sVal" }
//                    ^ here's the error
};

// --- --- --- 

/* case 2 */
interface ITopType_2 {
  tKey_2: string;
}

interface ISubType_2 extends ITopType_2 {
  sKey_2: string;
}

interface ITestInteface_2 {
  [pA_2: string]: ITopType_2;
  [pB_2: `sub_${string}`]: ISubType_2;
}

const testObj_2: ITestInteface_2 = {
  a: { tKey_2: "tVal_2 " },
  sub_b: { tKey_2: "tVal_2 ", sKey_2: "sVal_2" }
  //                            ^ here's the error
};

🙁 Actual behavior

The TypeScript handbook states that it is possible to support both types (string and number) of indexers, but the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. However, when attempting to implement this, I encountered an error.

/* case 1 */
interface ITopType {
  tKey: string;
}

interface ISubType extends ITopType {
  sKey: string;
}

interface ITestInteface {
  [pA: string]: ITopType;
  [pB: number]: ISubType;
}

const testObj: ITestInteface = {
  a: { tKey: "tVal" },
  1: { tKey: "tVal", sKey: "sVal" }
//                    ^ here's the error
};

The error:

Type '{ a: { tKey: string; }; 1: { tKey: string; sKey: string; }; }' is not assignable to type 'ITestInteface'.
  Property '1' is incompatible with index signature.
    Object literal may only specify known properties, and 'sKey' does not exist in type 'ITopType'.(2322)

There are several approaches to rectify this error (type assertion etc.), but they involve rewriting code and modifying constructs.

The same issue can occur here even without the use of a number index signature.


/* case 2 */
interface ITopType_2 {
  tKey_2: string;
}

interface ISubType_2 extends ITopType_2 {
  sKey_2: string;
}

interface ITestInteface_2 {
  [pA_2: string]: ITopType_2;
  [pB_2: `sub_${string}`]: ISubType_2;
}

const testObj_2: ITestInteface_2 = {
  a: { tKey_2: "tVal_2 " },
  sub_b: { tKey_2: "tVal_2 ", sKey_2: "sVal_2" }
  //                            ^ here's the error
};

The second error:

Type '{ a: { tKey_2: string; }; sub_b: { tKey_2: string; sKey_2: string; }; }' is not assignable to type 'ITestInteface_2'.
  Property 'sub_b' is incompatible with index signature.
    Object literal may only specify known properties, but 'sKey_2' does not exist in type 'ITopType_2'. Did you mean to write 'tKey_2'?(2322)

🙂 Expected behavior

The TypeScript compiler should allow assignment to the number index signature and the template string pattern index signature.

Additional information about the issue

No response

jcalz commented 5 months ago

I'm interested in seeing how this goes. I am also surprised that excess property checking would kick in here, or that there's seemingly no previous mention of this behavior in GitHub. It seems like such an obvious bug that surely people have encountered it before? What am I missing here?

snarbies commented 5 months ago

My first thought was that maybe in an object literal all indices are treated as strings (as opposed to an array literal where you have implicit numeric indices). But if you remove the string index signature, the error goes away. So I guess you could say a string index signature takes precedence in an object literal, but whether that's by design or a bug... ¯\_(ツ)_/¯ dunno

RyanCavanaugh commented 5 months ago

I'm not 100% sure this is "fixable" in a great way, but someone can certainly try.

Previous issues have complained about inconsistencies between { 1: expr } and { "1": expr }, which "should" be identical since they have actually-identical runtime semantics, but the intent of this code is quite clear, plus the equivalent assignment works:

// ok
testObj[1] = { tKey: "tVal", sKey: "sVal" };