From b93892a6f318ee9a78c2e7100e0c5382e9095b27 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:46:49 -0700 Subject: [PATCH] Computed property names are const contexts By analogy with the existing rule that element access argument expressions are a const context. Fixes #45798 --- src/compiler/checker.ts | 1 + .../constContextTemplateLiteral.errors.txt | 27 +++++++++++ .../reference/constContextTemplateLiteral.js | 31 ++++++++++++ .../constContextTemplateLiteral.symbols | 37 +++++++++++++++ .../constContextTemplateLiteral.types | 47 +++++++++++++++++++ .../literals/constContextTemplateLiteral.ts | 13 +++++ 6 files changed, 156 insertions(+) create mode 100644 tests/baselines/reference/constContextTemplateLiteral.errors.txt create mode 100644 tests/baselines/reference/constContextTemplateLiteral.js create mode 100644 tests/baselines/reference/constContextTemplateLiteral.symbols create mode 100644 tests/baselines/reference/constContextTemplateLiteral.types create mode 100644 tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d149b04428..9925478b31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33488,6 +33488,7 @@ namespace ts { const parent = node.parent; return isAssertionExpression(parent) && isConstTypeReference(parent.type) || isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + isComputedPropertyName(parent) || (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); } diff --git a/tests/baselines/reference/constContextTemplateLiteral.errors.txt b/tests/baselines/reference/constContextTemplateLiteral.errors.txt new file mode 100644 index 0000000000..2f084fda02 --- /dev/null +++ b/tests/baselines/reference/constContextTemplateLiteral.errors.txt @@ -0,0 +1,27 @@ +tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts(10,24): error TS2418: Type of computed property's value is '{ b: string; }', which is not assignable to type '{ a: any; }'. + Object literal may only specify known properties, and 'b' does not exist in type '{ a: any; }'. +tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts(11,33): error TS2418: Type of computed property's value is '{ b: string; }', which is not assignable to type '{ a: any; }'. + Object literal may only specify known properties, and 'b' does not exist in type '{ a: any; }'. + + +==== tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts (2 errors) ==== + interface Person { + id: number; + name: string; + } + + declare function key(): `person-${number}` + /* This only happens if index type is a template literal type */ + const persons: Record<`person-${Person["id"]}`, { a: any }> = { + ...{}, + [`person-${1}`]: { b: "something" }, // ok, error + ~~~~~~~~~~~~~~ +!!! error TS2418: Type of computed property's value is '{ b: string; }', which is not assignable to type '{ a: any; }'. +!!! error TS2418: Object literal may only specify known properties, and 'b' does not exist in type '{ a: any; }'. + [`person-${1}` as const]: { b: "something" }, // ok, error + ~~~~~~~~~~~~~~ +!!! error TS2418: Type of computed property's value is '{ b: string; }', which is not assignable to type '{ a: any; }'. +!!! error TS2418: Object literal may only specify known properties, and 'b' does not exist in type '{ a: any; }'. + [key()]: { b: "something" }, // still no error + } + \ No newline at end of file diff --git a/tests/baselines/reference/constContextTemplateLiteral.js b/tests/baselines/reference/constContextTemplateLiteral.js new file mode 100644 index 0000000000..10f4d01362 --- /dev/null +++ b/tests/baselines/reference/constContextTemplateLiteral.js @@ -0,0 +1,31 @@ +//// [constContextTemplateLiteral.ts] +interface Person { + id: number; + name: string; +} + +declare function key(): `person-${number}` +/* This only happens if index type is a template literal type */ +const persons: Record<`person-${Person["id"]}`, { a: any }> = { + ...{}, + [`person-${1}`]: { b: "something" }, // ok, error + [`person-${1}` as const]: { b: "something" }, // ok, error + [key()]: { b: "something" }, // still no error +} + + +//// [constContextTemplateLiteral.js] +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var _a; +/* This only happens if index type is a template literal type */ +var persons = __assign({}, (_a = {}, _a["person-".concat(1)] = { b: "something" }, _a["person-".concat(1)] = { b: "something" }, _a[key()] = { b: "something" }, _a)); diff --git a/tests/baselines/reference/constContextTemplateLiteral.symbols b/tests/baselines/reference/constContextTemplateLiteral.symbols new file mode 100644 index 0000000000..2d31f131f2 --- /dev/null +++ b/tests/baselines/reference/constContextTemplateLiteral.symbols @@ -0,0 +1,37 @@ +=== tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts === +interface Person { +>Person : Symbol(Person, Decl(constContextTemplateLiteral.ts, 0, 0)) + + id: number; +>id : Symbol(Person.id, Decl(constContextTemplateLiteral.ts, 0, 18)) + + name: string; +>name : Symbol(Person.name, Decl(constContextTemplateLiteral.ts, 1, 15)) +} + +declare function key(): `person-${number}` +>key : Symbol(key, Decl(constContextTemplateLiteral.ts, 3, 1)) + +/* This only happens if index type is a template literal type */ +const persons: Record<`person-${Person["id"]}`, { a: any }> = { +>persons : Symbol(persons, Decl(constContextTemplateLiteral.ts, 7, 5)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>Person : Symbol(Person, Decl(constContextTemplateLiteral.ts, 0, 0)) +>a : Symbol(a, Decl(constContextTemplateLiteral.ts, 7, 49)) + + ...{}, + [`person-${1}`]: { b: "something" }, // ok, error +>[`person-${1}`] : Symbol([`person-${1}`], Decl(constContextTemplateLiteral.ts, 8, 10)) +>b : Symbol(b, Decl(constContextTemplateLiteral.ts, 9, 22)) + + [`person-${1}` as const]: { b: "something" }, // ok, error +>[`person-${1}` as const] : Symbol([`person-${1}` as const], Decl(constContextTemplateLiteral.ts, 9, 40)) +>const : Symbol(const) +>b : Symbol(b, Decl(constContextTemplateLiteral.ts, 10, 31)) + + [key()]: { b: "something" }, // still no error +>[key()] : Symbol([key()], Decl(constContextTemplateLiteral.ts, 10, 49)) +>key : Symbol(key, Decl(constContextTemplateLiteral.ts, 3, 1)) +>b : Symbol(b, Decl(constContextTemplateLiteral.ts, 11, 14)) +} + diff --git a/tests/baselines/reference/constContextTemplateLiteral.types b/tests/baselines/reference/constContextTemplateLiteral.types new file mode 100644 index 0000000000..99fcfb91c1 --- /dev/null +++ b/tests/baselines/reference/constContextTemplateLiteral.types @@ -0,0 +1,47 @@ +=== tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts === +interface Person { + id: number; +>id : number + + name: string; +>name : string +} + +declare function key(): `person-${number}` +>key : () => `person-${number}` + +/* This only happens if index type is a template literal type */ +const persons: Record<`person-${Person["id"]}`, { a: any }> = { +>persons : Record<`person-${number}`, { a: any; }> +>a : any +>{ ...{}, [`person-${1}`]: { b: "something" }, // ok, error [`person-${1}` as const]: { b: "something" }, // ok, error [key()]: { b: "something" }, // still no error} : { "person-1": { b: string; }; } + + ...{}, +>{} : {} + + [`person-${1}`]: { b: "something" }, // ok, error +>[`person-${1}`] : { b: string; } +>`person-${1}` : "person-1" +>1 : 1 +>{ b: "something" } : { b: string; } +>b : string +>"something" : "something" + + [`person-${1}` as const]: { b: "something" }, // ok, error +>[`person-${1}` as const] : { b: string; } +>`person-${1}` as const : "person-1" +>`person-${1}` : "person-1" +>1 : 1 +>{ b: "something" } : { b: string; } +>b : string +>"something" : "something" + + [key()]: { b: "something" }, // still no error +>[key()] : { b: string; } +>key() : `person-${number}` +>key : () => `person-${number}` +>{ b: "something" } : { b: string; } +>b : string +>"something" : "something" +} + diff --git a/tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts b/tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts new file mode 100644 index 0000000000..c40bc2bbad --- /dev/null +++ b/tests/cases/conformance/expressions/literals/constContextTemplateLiteral.ts @@ -0,0 +1,13 @@ +interface Person { + id: number; + name: string; +} + +declare function key(): `person-${number}` +/* This only happens if index type is a template literal type */ +const persons: Record<`person-${Person["id"]}`, { a: any }> = { + ...{}, + [`person-${1}`]: { b: "something" }, // ok, error + [`person-${1}` as const]: { b: "something" }, // ok, error + [key()]: { b: "something" }, // still no error +}