Skip to content

Grab the left type when narrowing by in operator lazily #54795

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

Merged
merged 2 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27167,14 +27167,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue);
}
const target = getReferenceCandidate(expr.right);
const leftType = getTypeOfExpression(expr.left);
if (leftType.flags & TypeFlags.StringOrNumberLiteralOrUnique) {
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target) &&
getAccessedPropertyName(reference) === getPropertyNameFromType(leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType)) {
if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) {
const leftType = getTypeOfExpression(expr.left);
if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) {
return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined);
}
if (isMatchingReference(reference, target)) {
return narrowTypeByInKeyword(type, leftType as StringLiteralType | NumberLiteralType | UniqueESSymbolType, assumeTrue);
}
if (isMatchingReference(reference, target)) {
const leftType = getTypeOfExpression(expr.left);
if (isTypeUsableAsPropertyName(leftType)) {
return narrowTypeByInKeyword(type, leftType, assumeTrue);
}
}
break;
Expand Down
71 changes: 71 additions & 0 deletions tests/baselines/reference/controlFlowInOperator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,45 @@ if (a in c) {
if (d in c) {
c; // never
}

// repro from https://github.com/microsoft/TypeScript/issues/54790

function uniqueID_54790(
id: string | undefined,
seenIDs: { [key: string]: string }
): string {
if (id === undefined) {
id = "1";
}
if (!(id in seenIDs)) {
return id;
}
for (let i = 1; i < Number.MAX_VALUE; i++) {
const newID = `${id}-${i}`;
if (!(newID in seenIDs)) {
return newID;
}
}
throw Error("heat death of the universe");
}

function uniqueID_54790_2(id: string | number, seenIDs: object) {
id = "a";
for (let i = 1; i < 3; i++) {
const newID = `${id}`;
if (newID in seenIDs) {
}
}
}

function uniqueID_54790_3(id: string | number, seenIDs: object) {
id = "a";
for (let i = 1; i < 3; i++) {
const newID = id;
if (newID in seenIDs) {
}
}
}


//// [controlFlowInOperator.js]
Expand All @@ -47,3 +86,35 @@ if (a in c) {
if (d in c) {
c; // never
}
// repro from https://github.com/microsoft/TypeScript/issues/54790
function uniqueID_54790(id, seenIDs) {
if (id === undefined) {
id = "1";
}
if (!(id in seenIDs)) {
return id;
}
for (var i = 1; i < Number.MAX_VALUE; i++) {
var newID = "".concat(id, "-").concat(i);
if (!(newID in seenIDs)) {
return newID;
}
}
throw Error("heat death of the universe");
}
function uniqueID_54790_2(id, seenIDs) {
id = "a";
for (var i = 1; i < 3; i++) {
var newID = "".concat(id);
if (newID in seenIDs) {
}
}
}
function uniqueID_54790_3(id, seenIDs) {
id = "a";
for (var i = 1; i < 3; i++) {
var newID = id;
if (newID in seenIDs) {
}
}
}
100 changes: 100 additions & 0 deletions tests/baselines/reference/controlFlowInOperator.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,103 @@ if (d in c) {
>c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13))
}

// repro from https://github.com/microsoft/TypeScript/issues/54790

function uniqueID_54790(
>uniqueID_54790 : Symbol(uniqueID_54790, Decl(controlFlowInOperator.ts, 25, 1))

id: string | undefined,
>id : Symbol(id, Decl(controlFlowInOperator.ts, 29, 24))

seenIDs: { [key: string]: string }
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 30, 25))
>key : Symbol(key, Decl(controlFlowInOperator.ts, 31, 14))

): string {
if (id === undefined) {
>id : Symbol(id, Decl(controlFlowInOperator.ts, 29, 24))
>undefined : Symbol(undefined)

id = "1";
>id : Symbol(id, Decl(controlFlowInOperator.ts, 29, 24))
}
if (!(id in seenIDs)) {
>id : Symbol(id, Decl(controlFlowInOperator.ts, 29, 24))
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 30, 25))

return id;
>id : Symbol(id, Decl(controlFlowInOperator.ts, 29, 24))
}
for (let i = 1; i < Number.MAX_VALUE; i++) {
>i : Symbol(i, Decl(controlFlowInOperator.ts, 39, 10))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 39, 10))
>Number.MAX_VALUE : Symbol(NumberConstructor.MAX_VALUE, Decl(lib.es5.d.ts, --, --))
>Number : Symbol(Number, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>MAX_VALUE : Symbol(NumberConstructor.MAX_VALUE, Decl(lib.es5.d.ts, --, --))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 39, 10))

const newID = `${id}-${i}`;
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 40, 9))
>id : Symbol(id, Decl(controlFlowInOperator.ts, 29, 24))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 39, 10))

if (!(newID in seenIDs)) {
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 40, 9))
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 30, 25))

return newID;
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 40, 9))
}
}
throw Error("heat death of the universe");
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}

function uniqueID_54790_2(id: string | number, seenIDs: object) {
>uniqueID_54790_2 : Symbol(uniqueID_54790_2, Decl(controlFlowInOperator.ts, 46, 1))
>id : Symbol(id, Decl(controlFlowInOperator.ts, 48, 26))
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 48, 46))

id = "a";
>id : Symbol(id, Decl(controlFlowInOperator.ts, 48, 26))

for (let i = 1; i < 3; i++) {
>i : Symbol(i, Decl(controlFlowInOperator.ts, 50, 10))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 50, 10))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 50, 10))

const newID = `${id}`;
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 51, 9))
>id : Symbol(id, Decl(controlFlowInOperator.ts, 48, 26))

if (newID in seenIDs) {
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 51, 9))
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 48, 46))
}
}
}

function uniqueID_54790_3(id: string | number, seenIDs: object) {
>uniqueID_54790_3 : Symbol(uniqueID_54790_3, Decl(controlFlowInOperator.ts, 55, 1))
>id : Symbol(id, Decl(controlFlowInOperator.ts, 57, 26))
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 57, 46))

id = "a";
>id : Symbol(id, Decl(controlFlowInOperator.ts, 57, 26))

for (let i = 1; i < 3; i++) {
>i : Symbol(i, Decl(controlFlowInOperator.ts, 59, 10))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 59, 10))
>i : Symbol(i, Decl(controlFlowInOperator.ts, 59, 10))

const newID = id;
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 60, 9))
>id : Symbol(id, Decl(controlFlowInOperator.ts, 57, 26))

if (newID in seenIDs) {
>newID : Symbol(newID, Decl(controlFlowInOperator.ts, 60, 9))
>seenIDs : Symbol(seenIDs, Decl(controlFlowInOperator.ts, 57, 46))
}
}
}

130 changes: 130 additions & 0 deletions tests/baselines/reference/controlFlowInOperator.types
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,133 @@ if (d in c) {
>c : (A | B) & Record<"d", unknown>
}

// repro from https://github.com/microsoft/TypeScript/issues/54790

function uniqueID_54790(
>uniqueID_54790 : (id: string | undefined, seenIDs: { [key: string]: string; }) => string

id: string | undefined,
>id : string

seenIDs: { [key: string]: string }
>seenIDs : { [key: string]: string; }
>key : string

): string {
if (id === undefined) {
>id === undefined : boolean
>id : string
>undefined : undefined

id = "1";
>id = "1" : "1"
>id : string
>"1" : "1"
}
if (!(id in seenIDs)) {
>!(id in seenIDs) : boolean
>(id in seenIDs) : boolean
>id in seenIDs : boolean
>id : string
>seenIDs : { [key: string]: string; }

return id;
>id : string
}
for (let i = 1; i < Number.MAX_VALUE; i++) {
>i : number
>1 : 1
>i < Number.MAX_VALUE : boolean
>i : number
>Number.MAX_VALUE : number
>Number : NumberConstructor
>MAX_VALUE : number
>i++ : number
>i : number

const newID = `${id}-${i}`;
>newID : string
>`${id}-${i}` : string
>id : string
>i : number

if (!(newID in seenIDs)) {
>!(newID in seenIDs) : boolean
>(newID in seenIDs) : boolean
>newID in seenIDs : boolean
>newID : string
>seenIDs : { [key: string]: string; }

return newID;
>newID : string
}
}
throw Error("heat death of the universe");
>Error("heat death of the universe") : Error
>Error : ErrorConstructor
>"heat death of the universe" : "heat death of the universe"
}

function uniqueID_54790_2(id: string | number, seenIDs: object) {
>uniqueID_54790_2 : (id: string | number, seenIDs: object) => void
>id : string | number
>seenIDs : object

id = "a";
>id = "a" : "a"
>id : string | number
>"a" : "a"

for (let i = 1; i < 3; i++) {
>i : number
>1 : 1
>i < 3 : boolean
>i : number
>3 : 3
>i++ : number
>i : number

const newID = `${id}`;
>newID : string
>`${id}` : string
>id : string

if (newID in seenIDs) {
>newID in seenIDs : boolean
>newID : string
>seenIDs : object
}
}
}

function uniqueID_54790_3(id: string | number, seenIDs: object) {
>uniqueID_54790_3 : (id: string | number, seenIDs: object) => void
>id : string | number
>seenIDs : object

id = "a";
>id = "a" : "a"
>id : string | number
>"a" : "a"

for (let i = 1; i < 3; i++) {
>i : number
>1 : 1
>i < 3 : boolean
>i : number
>3 : 3
>i++ : number
>i : number

const newID = id;
>newID : string
>id : string

if (newID in seenIDs) {
>newID in seenIDs : boolean
>newID : string
>seenIDs : object
}
}
}

39 changes: 39 additions & 0 deletions tests/cases/conformance/controlFlow/controlFlowInOperator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,42 @@ if (a in c) {
if (d in c) {
c; // never
}

// repro from https://github.com/microsoft/TypeScript/issues/54790

function uniqueID_54790(
id: string | undefined,
seenIDs: { [key: string]: string }
): string {
if (id === undefined) {
id = "1";
}
if (!(id in seenIDs)) {
return id;
}
for (let i = 1; i < Number.MAX_VALUE; i++) {
const newID = `${id}-${i}`;
if (!(newID in seenIDs)) {
return newID;
}
}
throw Error("heat death of the universe");
}

function uniqueID_54790_2(id: string | number, seenIDs: object) {
id = "a";
for (let i = 1; i < 3; i++) {
const newID = `${id}`;
if (newID in seenIDs) {
}
}
}

function uniqueID_54790_3(id: string | number, seenIDs: object) {
id = "a";
for (let i = 1; i < 3; i++) {
const newID = id;
if (newID in seenIDs) {
}
}
}