From 57ebd99ff25b87bcf598f79f506e051f59b8a9c4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 9 Feb 2023 07:06:09 -0800 Subject: [PATCH] Exclude `typeof this` from check in `isConstantReference` (#52680) --- src/compiler/checker.ts | 10 ++- .../reference/thisInTypeQuery.symbols | 77 ++++++++++++++++ .../baselines/reference/thisInTypeQuery.types | 89 +++++++++++++++++++ tests/cases/compiler/thisInTypeQuery.ts | 32 +++++++ 4 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/thisInTypeQuery.symbols create mode 100644 tests/baselines/reference/thisInTypeQuery.types create mode 100644 tests/cases/compiler/thisInTypeQuery.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe687cc3be0..5200b9b4caf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26091,10 +26091,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isConstantReference(node: Node): boolean { switch (node.kind) { - case SyntaxKind.Identifier: { - const symbol = getResolvedSymbol(node as Identifier); - return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); - } + case SyntaxKind.Identifier: + if (!isThisInTypeQuery(node)) { + const symbol = getResolvedSymbol(node as Identifier); + return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); + } + break; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. diff --git a/tests/baselines/reference/thisInTypeQuery.symbols b/tests/baselines/reference/thisInTypeQuery.symbols new file mode 100644 index 00000000000..3fdf5f6d869 --- /dev/null +++ b/tests/baselines/reference/thisInTypeQuery.symbols @@ -0,0 +1,77 @@ +=== tests/cases/compiler/thisInTypeQuery.ts === +// Repros from #52672 + +function assert(condition: unknown): asserts condition { +>assert : Symbol(assert, Decl(thisInTypeQuery.ts, 0, 0)) +>condition : Symbol(condition, Decl(thisInTypeQuery.ts, 2, 16)) +>condition : Symbol(condition, Decl(thisInTypeQuery.ts, 2, 16)) + + if (!condition) { +>condition : Symbol(condition, Decl(thisInTypeQuery.ts, 2, 16)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +class MyClass { +>MyClass : Symbol(MyClass, Decl(thisInTypeQuery.ts, 6, 1)) + + private map = { +>map : Symbol(MyClass.map, Decl(thisInTypeQuery.ts, 8, 15)) + + my_key: 'example_value' +>my_key : Symbol(my_key, Decl(thisInTypeQuery.ts, 9, 19)) + + }; + + runTypeFails() { +>runTypeFails : Symbol(MyClass.runTypeFails, Decl(thisInTypeQuery.ts, 11, 6)) + + const params = null as any as { a: { key: string } } | null; +>params : Symbol(params, Decl(thisInTypeQuery.ts, 14, 13)) +>a : Symbol(a, Decl(thisInTypeQuery.ts, 14, 39)) +>key : Symbol(key, Decl(thisInTypeQuery.ts, 14, 44)) + + assert(params); +>assert : Symbol(assert, Decl(thisInTypeQuery.ts, 0, 0)) +>params : Symbol(params, Decl(thisInTypeQuery.ts, 14, 13)) + + type Key = keyof typeof this.map; +>Key : Symbol(Key, Decl(thisInTypeQuery.ts, 15, 23)) +>this.map : Symbol(MyClass.map, Decl(thisInTypeQuery.ts, 8, 15)) +>this : Symbol(MyClass, Decl(thisInTypeQuery.ts, 6, 1)) +>map : Symbol(MyClass.map, Decl(thisInTypeQuery.ts, 8, 15)) + + this.map[params.a.key as Key]; +>this.map : Symbol(MyClass.map, Decl(thisInTypeQuery.ts, 8, 15)) +>this : Symbol(MyClass, Decl(thisInTypeQuery.ts, 6, 1)) +>map : Symbol(MyClass.map, Decl(thisInTypeQuery.ts, 8, 15)) +>params.a.key : Symbol(key, Decl(thisInTypeQuery.ts, 14, 44)) +>params.a : Symbol(a, Decl(thisInTypeQuery.ts, 14, 39)) +>params : Symbol(params, Decl(thisInTypeQuery.ts, 14, 13)) +>a : Symbol(a, Decl(thisInTypeQuery.ts, 14, 39)) +>key : Symbol(key, Decl(thisInTypeQuery.ts, 14, 44)) +>Key : Symbol(Key, Decl(thisInTypeQuery.ts, 15, 23)) + } +} + +class C { +>C : Symbol(C, Decl(thisInTypeQuery.ts, 19, 1)) + + foo() { +>foo : Symbol(C.foo, Decl(thisInTypeQuery.ts, 21, 9)) + + const x = !!true; +>x : Symbol(x, Decl(thisInTypeQuery.ts, 23, 9)) + + if (x) { +>x : Symbol(x, Decl(thisInTypeQuery.ts, 23, 9)) + + type T0 = typeof this; +>T0 : Symbol(T0, Decl(thisInTypeQuery.ts, 24, 12)) +>this : Symbol(C, Decl(thisInTypeQuery.ts, 19, 1)) + } + } +} + diff --git a/tests/baselines/reference/thisInTypeQuery.types b/tests/baselines/reference/thisInTypeQuery.types new file mode 100644 index 00000000000..887aee40100 --- /dev/null +++ b/tests/baselines/reference/thisInTypeQuery.types @@ -0,0 +1,89 @@ +=== tests/cases/compiler/thisInTypeQuery.ts === +// Repros from #52672 + +function assert(condition: unknown): asserts condition { +>assert : (condition: unknown) => asserts condition +>condition : unknown + + if (!condition) { +>!condition : boolean +>condition : unknown + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } +} + +class MyClass { +>MyClass : MyClass + + private map = { +>map : { my_key: string; } +>{ my_key: 'example_value' } : { my_key: string; } + + my_key: 'example_value' +>my_key : string +>'example_value' : "example_value" + + }; + + runTypeFails() { +>runTypeFails : () => void + + const params = null as any as { a: { key: string } } | null; +>params : { a: { key: string;}; } | null +>null as any as { a: { key: string } } | null : { a: { key: string;}; } | null +>null as any : any +>null : null +>a : { key: string; } +>key : string +>null : null + + assert(params); +>assert(params) : void +>assert : (condition: unknown) => asserts condition +>params : { a: { key: string; }; } | null + + type Key = keyof typeof this.map; +>Key : "my_key" +>this.map : { my_key: string; } +>this : this +>map : { my_key: string; } + + this.map[params.a.key as Key]; +>this.map[params.a.key as Key] : string +>this.map : { my_key: string; } +>this : this +>map : { my_key: string; } +>params.a.key as Key : "my_key" +>params.a.key : string +>params.a : { key: string; } +>params : { a: { key: string; }; } +>a : { key: string; } +>key : string + } +} + +class C { +>C : C + + foo() { +>foo : () => void + + const x = !!true; +>x : true +>!!true : true +>!true : false +>true : true + + if (x) { +>x : true + + type T0 = typeof this; +>T0 : this +>this : this + } + } +} + diff --git a/tests/cases/compiler/thisInTypeQuery.ts b/tests/cases/compiler/thisInTypeQuery.ts new file mode 100644 index 00000000000..080fb3b4fe9 --- /dev/null +++ b/tests/cases/compiler/thisInTypeQuery.ts @@ -0,0 +1,32 @@ +// @strict: true +// @noEmit: true + +// Repros from #52672 + +function assert(condition: unknown): asserts condition { + if (!condition) { + throw new Error(); + } +} + +class MyClass { + private map = { + my_key: 'example_value' + }; + + runTypeFails() { + const params = null as any as { a: { key: string } } | null; + assert(params); + type Key = keyof typeof this.map; + this.map[params.a.key as Key]; + } +} + +class C { + foo() { + const x = !!true; + if (x) { + type T0 = typeof this; + } + } +}