microsoft / TypeScript

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

Type 'never' incorrectly inferred for mixed primitive type properties assignment with strictNullChecks disabled #59969

Open heguro opened 1 month ago

heguro commented 1 month ago

🔎 Search Terms

strictNullChecks false assign never

🕗 Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?strictNullChecks=false&ts=5.6.2#code/C4TwDgpgBA8gRgKygXigbwFBSmATgezAEYB+ALigGdhcBLAOwHMBuLHAsAJnKpoZbZ5CAZh70ArgFs4EXK2xCwAFh4ARAIbAIrAL6sMAegNQAwusrQiFAIIAbW+0KzgtCJSgALdQDdowD9CU6pJ+4NAAFNR0TACUGADG+PTUUABmRChQ4fiIFPAIADRQANYQIBQARIpEFVAAPlBVHJwVMSgAfOhsOQgA2qUgALqZ4vQAJhCpDBBjzFBGUABq6ra0Y92I-WXDqBL2cwvLq2NQAO4B9Lx08cAAcuL2JgHxxe607qkrFhh6GIbGZgsUE4FAAChxnK53F5fFAxrRUqlZBB6MB2LRJLQXLDQJB3JE+Ex6lAJNJZHFEsk0alOJlsrlYIgigNKtVag0miJWh0utgelshiNxpNprN5sYAKK4Ai4M4XK60G73R7PV5Qd5pL4QAB0UAlAA9IDcZlBgPgoDIoN4VmsNn0BjsSQ9bAdJdL8LhdQajVoTmaLdBrcc5SiFUrnU8IC83h8tWxKSkg5l+Q7XUsbSdwrjoBqovxiaSZLg4mwFgB1D3FdQEUbrbAJtE9WmoHpQczoao8Wsi+gzIqKUQUbtTXtjX58xCcAWO4eitNHW0ThBTh2ZPYu8Xp4PnUN58MqqNqjWfWzfbCHDNQCC+S6nLEeKAAAwg+vUNxgYBcSRW4KcuFAAAqYSUBQNDiBAj5FP4+DiIwD4yDCtAenCCJIrglBsBMtgQFoUAptsuj6AsgLQMIYIQv+ULqvQ8S2OIEwWvg-jopi2LQOo4x4YgUZotm+J5kSDQaFoFJJCkqTCHSPR5EyJRlKyHA1MSnLKNyyCdJgS7TkKEwjjM84Zna2m7M6BnbvKe53BGqoxpqp4QD8zBAA

💻 Code

type Obj = {
  prop1?: string;
  prop2?: string;
  prop3?: number;
  prop4?: Date;
};

// Case 1: All properties have the same type (string)
const f1 = (obj: Obj, key: "prop1" | "prop2") => {
  obj[key] = undefined; // Valid
  obj[key] = null; // Valid when strictNullChecks is false
};

// Case 2: Properties have different primitive types (string | number)
const f2 = (obj: Obj, key: "prop1" | "prop3") => {
  obj[key] = undefined; // Error when strictNullChecks is false. Expected to be valid
  obj[key] = null; // Error. Expected to be valid when strictNullChecks is false
  const val = obj[key]; // Valid (type is string | number)

  // Workaround
  const obj2 = obj as {prop1?: undefined, prop3?: undefined};
  obj2[key] = undefined; // Valid
  obj2[key] = null; // Valid when strictNullChecks is false
  // Valid even with `exactOptionalPropertyTypes: true`, though behavior differs
  delete obj[key];
};

// Case 3: Properties include both primitive and object types (string | Date)
const f3 = (obj: Obj, key: "prop1" | "prop4") => {
  obj[key] = undefined; // Valid
  obj[key] = null; // Valid when strictNullChecks is false
};

🙁 Actual behavior

  1. In Case 2, when strictNullChecks is false:

    • Assigning undefined results in an error: "Type 'undefined' is not assignable to type 'never'.(TS2322)"
    • Assigning null always results in an error: "Type 'null' is not assignable to type 'never'.(TS2322)"
  2. The behavior is inconsistent across different combinations of property types.

🙂 Expected behavior

Since all properties in Obj are optional, it should be possible to assign undefined to obj[key] regardless of the strictNullChecks setting, unless exactOptionalPropertyTypes is true (which cannot be used with strictNullChecks: false).

Additionally, when strictNullChecks is false, assigning null should be allowed for all cases.

Additional information about the issue

No response

MartinJohns commented 1 month ago

Since all properties in Obj are optional, it should be possible to assign undefined to obj[key] regardless of the strictNullChecks setting.

That would depend on the exactOptionalPropertyTypes setting.

heguro commented 1 month ago

That would depend on the exactOptionalPropertyTypes setting.

I didn't know that. However, at least in TypeScript 5.6.2 and 5.7.0-dev.20240914, it seems that strictNullChecks=false and exactOptionalPropertyTypes=true cannot be used together. image

Edit: Added reference to exactOptionalPropertyTypes in Expected behavior. Edit 2: Added delete workground, which can be used with exactOptionalPropertyTypes: true.