From 370d34cdca7e3ab1d64ccea82040d9292fadbdda Mon Sep 17 00:00:00 2001 From: Zzzen Date: Wed, 6 Apr 2022 00:33:19 +0800 Subject: [PATCH] narrow type for generic variables inside TypeQuery (#48434) --- src/compiler/checker.ts | 1 + .../narrowingOfQualifiedNames.errors.txt | 21 ++++++ .../reference/narrowingOfQualifiedNames.js | 33 +++++++++ .../narrowingOfQualifiedNames.symbols | 73 +++++++++++++++++++ .../reference/narrowingOfQualifiedNames.types | 73 +++++++++++++++++++ .../compiler/narrowingOfQualifiedNames.ts | 21 ++++++ 6 files changed, 222 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7e144dd26f3..a93ca5297bb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25311,6 +25311,7 @@ namespace ts { // a generic type without a nullable constraint and x is a generic type. This is because when both obj // and x are of generic types T and K, we want the resulting type to be T[K]. return parent.kind === SyntaxKind.PropertyAccessExpression || + parent.kind === SyntaxKind.QualifiedName || parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); diff --git a/tests/baselines/reference/narrowingOfQualifiedNames.errors.txt b/tests/baselines/reference/narrowingOfQualifiedNames.errors.txt index 588ee1d5da1..80551ebc33d 100644 --- a/tests/baselines/reference/narrowingOfQualifiedNames.errors.txt +++ b/tests/baselines/reference/narrowingOfQualifiedNames.errors.txt @@ -71,4 +71,25 @@ tests/cases/compiler/narrowingOfQualifiedNames.ts(38,29): error TS2532: Object i } } } + } + + // Repro from #48289 + + type Fish = { type: 'fish', hasFins: true } + type Dog = { type: 'dog', saysWoof: true } + + type Pet = Fish | Dog; + + function handleDogBroken(pet: PetType) { + if(pet.type === 'dog') { + const _okay1 = pet.saysWoof; + const _okay2: typeof pet.saysWoof = pet.saysWoof; + } + } + + function handleDogWorking(pet: Pet) { + if(pet.type === 'dog') { + const _okay1 = pet.saysWoof; + const _okay2: typeof pet.saysWoof = pet.saysWoof; + } } \ No newline at end of file diff --git a/tests/baselines/reference/narrowingOfQualifiedNames.js b/tests/baselines/reference/narrowingOfQualifiedNames.js index e3d2816849e..c6ce04dba5c 100644 --- a/tests/baselines/reference/narrowingOfQualifiedNames.js +++ b/tests/baselines/reference/narrowingOfQualifiedNames.js @@ -63,6 +63,27 @@ function init2(foo: DeepOptional) { } } } +} + +// Repro from #48289 + +type Fish = { type: 'fish', hasFins: true } +type Dog = { type: 'dog', saysWoof: true } + +type Pet = Fish | Dog; + +function handleDogBroken(pet: PetType) { + if(pet.type === 'dog') { + const _okay1 = pet.saysWoof; + const _okay2: typeof pet.saysWoof = pet.saysWoof; + } +} + +function handleDogWorking(pet: Pet) { + if(pet.type === 'dog') { + const _okay1 = pet.saysWoof; + const _okay2: typeof pet.saysWoof = pet.saysWoof; + } } //// [narrowingOfQualifiedNames.js] @@ -94,3 +115,15 @@ function init2(foo) { } } } +function handleDogBroken(pet) { + if (pet.type === 'dog') { + var _okay1 = pet.saysWoof; + var _okay2 = pet.saysWoof; + } +} +function handleDogWorking(pet) { + if (pet.type === 'dog') { + var _okay1 = pet.saysWoof; + var _okay2 = pet.saysWoof; + } +} diff --git a/tests/baselines/reference/narrowingOfQualifiedNames.symbols b/tests/baselines/reference/narrowingOfQualifiedNames.symbols index f7a4e26d713..1c2a585c585 100644 --- a/tests/baselines/reference/narrowingOfQualifiedNames.symbols +++ b/tests/baselines/reference/narrowingOfQualifiedNames.symbols @@ -253,3 +253,76 @@ function init2(foo: DeepOptional) { } } } + +// Repro from #48289 + +type Fish = { type: 'fish', hasFins: true } +>Fish : Symbol(Fish, Decl(narrowingOfQualifiedNames.ts, 64, 1)) +>type : Symbol(type, Decl(narrowingOfQualifiedNames.ts, 68, 13)) +>hasFins : Symbol(hasFins, Decl(narrowingOfQualifiedNames.ts, 68, 27)) + +type Dog = { type: 'dog', saysWoof: true } +>Dog : Symbol(Dog, Decl(narrowingOfQualifiedNames.ts, 68, 43)) +>type : Symbol(type, Decl(narrowingOfQualifiedNames.ts, 69, 12)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) + +type Pet = Fish | Dog; +>Pet : Symbol(Pet, Decl(narrowingOfQualifiedNames.ts, 69, 42)) +>Fish : Symbol(Fish, Decl(narrowingOfQualifiedNames.ts, 64, 1)) +>Dog : Symbol(Dog, Decl(narrowingOfQualifiedNames.ts, 68, 43)) + +function handleDogBroken(pet: PetType) { +>handleDogBroken : Symbol(handleDogBroken, Decl(narrowingOfQualifiedNames.ts, 71, 22)) +>PetType : Symbol(PetType, Decl(narrowingOfQualifiedNames.ts, 73, 25)) +>Pet : Symbol(Pet, Decl(narrowingOfQualifiedNames.ts, 69, 42)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 73, 46)) +>PetType : Symbol(PetType, Decl(narrowingOfQualifiedNames.ts, 73, 25)) + + if(pet.type === 'dog') { +>pet.type : Symbol(type, Decl(narrowingOfQualifiedNames.ts, 68, 13), Decl(narrowingOfQualifiedNames.ts, 69, 12)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 73, 46)) +>type : Symbol(type, Decl(narrowingOfQualifiedNames.ts, 68, 13), Decl(narrowingOfQualifiedNames.ts, 69, 12)) + + const _okay1 = pet.saysWoof; +>_okay1 : Symbol(_okay1, Decl(narrowingOfQualifiedNames.ts, 75, 13)) +>pet.saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 73, 46)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) + + const _okay2: typeof pet.saysWoof = pet.saysWoof; +>_okay2 : Symbol(_okay2, Decl(narrowingOfQualifiedNames.ts, 76, 13)) +>pet.saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 73, 46)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet.saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 73, 46)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) + } +} + +function handleDogWorking(pet: Pet) { +>handleDogWorking : Symbol(handleDogWorking, Decl(narrowingOfQualifiedNames.ts, 78, 1)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 80, 26)) +>Pet : Symbol(Pet, Decl(narrowingOfQualifiedNames.ts, 69, 42)) + + if(pet.type === 'dog') { +>pet.type : Symbol(type, Decl(narrowingOfQualifiedNames.ts, 68, 13), Decl(narrowingOfQualifiedNames.ts, 69, 12)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 80, 26)) +>type : Symbol(type, Decl(narrowingOfQualifiedNames.ts, 68, 13), Decl(narrowingOfQualifiedNames.ts, 69, 12)) + + const _okay1 = pet.saysWoof; +>_okay1 : Symbol(_okay1, Decl(narrowingOfQualifiedNames.ts, 82, 13)) +>pet.saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 80, 26)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) + + const _okay2: typeof pet.saysWoof = pet.saysWoof; +>_okay2 : Symbol(_okay2, Decl(narrowingOfQualifiedNames.ts, 83, 13)) +>pet.saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 80, 26)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet.saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) +>pet : Symbol(pet, Decl(narrowingOfQualifiedNames.ts, 80, 26)) +>saysWoof : Symbol(saysWoof, Decl(narrowingOfQualifiedNames.ts, 69, 25)) + } +} diff --git a/tests/baselines/reference/narrowingOfQualifiedNames.types b/tests/baselines/reference/narrowingOfQualifiedNames.types index f9ec86f075a..59349ce8e18 100644 --- a/tests/baselines/reference/narrowingOfQualifiedNames.types +++ b/tests/baselines/reference/narrowingOfQualifiedNames.types @@ -257,3 +257,76 @@ function init2(foo: DeepOptional) { } } } + +// Repro from #48289 + +type Fish = { type: 'fish', hasFins: true } +>Fish : Fish +>type : "fish" +>hasFins : true +>true : true + +type Dog = { type: 'dog', saysWoof: true } +>Dog : Dog +>type : "dog" +>saysWoof : true +>true : true + +type Pet = Fish | Dog; +>Pet : Pet + +function handleDogBroken(pet: PetType) { +>handleDogBroken : (pet: PetType) => void +>pet : PetType + + if(pet.type === 'dog') { +>pet.type === 'dog' : boolean +>pet.type : "fish" | "dog" +>pet : Pet +>type : "fish" | "dog" +>'dog' : "dog" + + const _okay1 = pet.saysWoof; +>_okay1 : true +>pet.saysWoof : true +>pet : Dog +>saysWoof : true + + const _okay2: typeof pet.saysWoof = pet.saysWoof; +>_okay2 : true +>pet.saysWoof : true +>pet : Dog +>saysWoof : true +>pet.saysWoof : true +>pet : Dog +>saysWoof : true + } +} + +function handleDogWorking(pet: Pet) { +>handleDogWorking : (pet: Pet) => void +>pet : Pet + + if(pet.type === 'dog') { +>pet.type === 'dog' : boolean +>pet.type : "fish" | "dog" +>pet : Pet +>type : "fish" | "dog" +>'dog' : "dog" + + const _okay1 = pet.saysWoof; +>_okay1 : true +>pet.saysWoof : true +>pet : Dog +>saysWoof : true + + const _okay2: typeof pet.saysWoof = pet.saysWoof; +>_okay2 : true +>pet.saysWoof : true +>pet : Dog +>saysWoof : true +>pet.saysWoof : true +>pet : Dog +>saysWoof : true + } +} diff --git a/tests/cases/compiler/narrowingOfQualifiedNames.ts b/tests/cases/compiler/narrowingOfQualifiedNames.ts index 45dcf5e55c0..e74da83c954 100644 --- a/tests/cases/compiler/narrowingOfQualifiedNames.ts +++ b/tests/cases/compiler/narrowingOfQualifiedNames.ts @@ -64,4 +64,25 @@ function init2(foo: DeepOptional) { } } } +} + +// Repro from #48289 + +type Fish = { type: 'fish', hasFins: true } +type Dog = { type: 'dog', saysWoof: true } + +type Pet = Fish | Dog; + +function handleDogBroken(pet: PetType) { + if(pet.type === 'dog') { + const _okay1 = pet.saysWoof; + const _okay2: typeof pet.saysWoof = pet.saysWoof; + } +} + +function handleDogWorking(pet: Pet) { + if(pet.type === 'dog') { + const _okay1 = pet.saysWoof; + const _okay2: typeof pet.saysWoof = pet.saysWoof; + } } \ No newline at end of file