Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Since 4.8.2, union discrimination fails with (string & {}) when noUncheckedIndexedAccess is true #50683

Closed
maxijonson opened this issue Sep 8, 2022 · 6 comments · Fixed by #50704
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@maxijonson
Copy link

maxijonson commented Sep 8, 2022

Bug Report

🔎 Search Terms

noUncheckedIndexedAccess
string & {}
union
intersection reduction

🕗 Version & Regression Information

4.8.2

  • This changed between versions 4.7 and 4.8
  • Possibly related to this PR, where (string & {}) should be an exception
  • Only when noUncheckedIndexedAccess: true

⏯ Playground Link

Playground link with relevant code

💻 Code

// TS 4.8.2
// This does not fail by default under strict mode, since noUncheckedIndexedAccess is false by default
// Switch noUncheckedIndexedAccess to true to see the error
// Then switch to 4.7.4 to see the error disappear

type Alignment = (string & {}) | "left" | "center" | "right";
type Alignments = Record<Alignment, string>;

const a: Alignments = {
    left: "align-left",
    center: "align-center",
    right: "align-right",
    other: "align-other",
};

console.log(a.left.length); // This shouldn't be considered as possibly "undefined", since "left" is a defined Alignment (incorrect behaviour under 4.8.2)
console.log(a.other.length); // This should be considered as possibly "undefined" (correct behaviour)

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "strict": true,
    "noUncheckedIndexedAccess": true
  }
}

🙁 Actual behavior

TypeScript 4.8.2 reports that an object's property is possibly "undefined", even for declared fields in the union.

🙂 Expected behavior

TypeScript 4.8.2 should only report that an object's property is possibly "undefined" for un-declared fields, as stated in the documentation

@fatcerberus
Copy link

fatcerberus commented Sep 8, 2022

This seems to be the relevant difference:

// 4.7.4 hover
type Alignments = {
    [x: string & {}]: string;
    left: string;
    center: string;
    right: string;
}

// 4.8.2 hover
type Alignments = {
    [x: string]: string;
}

Alignment itself doesn't reduce to string - the difference is in the behavior of Record, which isn't covered at all in #49119.

@maxijonson
Copy link
Author

maxijonson commented Sep 9, 2022

This seems to be the relevant difference:

// 4.7.4 hover
type Alignments = {
    [x: string & {}]: string;
    left: string;
    center: string;
    right: string;
}

// 4.8.2 hover
type Alignments = {
    [x: string]: string;
}

Alignment itself doesn't reduce to string - the difference is in the behavior of Record, which isn't covered at all in #49119.

Thanks for narrowing down the inferred type! You're right, Record isn't covered in that PR. It's worth noting that this issue is relevant for non-Record too. The same expected behavior (4.7.4) and actual behavior (4.8.2) happens here:

type Alignment = (string & {}) | "left" | "center" | "right";
type Alignments = {
  [key in Alignment]: string;
};

const a: Alignments = {
  left: "align-left",
  center: "align-center",
  right: "align-right",
  other: "align-other",
};

console.log(a.left.length); // Fine under 4.7.4. Error under 4.8.2
console.log(a.other.length); // Error for both versions (expected, however, in 4.8.2, I don't believe it's failing for the 'right reason')

@lll000111
Copy link

lll000111 commented Sep 9, 2022

Not only unions. Here is a simpler example:

https://www.typescriptlang.org/play?ts=4.8.2#code/GYVwdgxgLglg9mABAZQKZgCYCEA2cBGAFBgIZQkBci4A1mHAO5gA0i6EcGMYA5lbfSYBKRAG8AsAChEMxDGCJC7Ttx6IAhAF5N1TKmDdUGRADITbSCt4btiAOQgowABx2RE6bK9QAFgCdGRDBUBkQAUT8Av0I7ZS5eNwBuKS8AXykU2QB6LMQAWTgQAGdURB84ADdUP0QoOEQS0oxUKFRoI1qATwAHUsyZHMQAdR90aiLVRAAVZEQKgBYAOmdFgCY5KDkisVTEAB9dZoNgjH7EQZGx4smZuaWAdkX5ja3EACJHFzf9w-1DU88MjiqmSklSoKAA

function SendBlob(data: unknown, encoding: unknown) {
    if (encoding !== undefined && encoding !== 'utf8') {
        throw new Error('encoding');
    }

    // Mouse hover to see detected type 
    // When using TS v4.8.2 it is {} | undefined
    // When using TS v4.7.4 it is "utf8" | undefined
    encoding;
};

@fatcerberus
Copy link

That’s… not even close to being the same problem as described here.

@maxijonson
Copy link
Author

While this is not in fact an issue relating to (string && {}) in declared union types or noUncheckedIndexAccess, I believe it could be related, since the conditions for encoding could create an "inferred" union.

@andrewbranch andrewbranch added the Needs Investigation This issue needs a team member to investigate its status. label Sep 9, 2022
@andrewbranch andrewbranch added this to the TypeScript 4.9.0 milestone Sep 9, 2022
@ahejlsberg ahejlsberg added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels Sep 9, 2022
@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label Sep 9, 2022
@lll000111
Copy link

For the record, definitely related, now its own ticket: #50706

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants