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

Const contexts for template literals #40707

Merged
merged 3 commits into from
Sep 22, 2020
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
24 changes: 12 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28212,6 +28212,7 @@ namespace ts {
case SyntaxKind.FalseKeyword:
case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.TemplateExpression:
return true;
case SyntaxKind.ParenthesizedExpression:
return isValidConstAssertionArgument((<ParenthesizedExpression>node).expression);
Expand Down Expand Up @@ -30284,18 +30285,17 @@ namespace ts {
}

function checkTemplateExpression(node: TemplateExpression): Type {
// We just want to check each expressions, but we are unconcerned with
// the type of each expression, as any value may be coerced into a string.
// It is worth asking whether this is what we really want though.
// A place where we actually *are* concerned with the expressions' types are
// in tagged templates.
forEach(node.templateSpans, templateSpan => {
if (maybeTypeOfKind(checkExpression(templateSpan.expression), TypeFlags.ESSymbolLike)) {
error(templateSpan.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String);
const texts = [node.head.text];
const types = [];
for (const span of node.templateSpans) {
const type = checkExpression(span.expression);
if (maybeTypeOfKind(type, TypeFlags.ESSymbolLike)) {
error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String);
}
});

return stringType;
texts.push(span.literal.text);
types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType);
}
return isConstContext(node) ? getTemplateLiteralType(texts, types) : stringType;
}

function getContextNode(node: Expression): Node {
Expand Down Expand Up @@ -30427,7 +30427,7 @@ namespace ts {
const parent = node.parent;
return isAssertionExpression(parent) && isConstTypeReference(parent.type) ||
(isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) ||
(isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent)) && isConstContext(parent.parent);
(isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent);
}

function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type {
Expand Down
46 changes: 45 additions & 1 deletion tests/baselines/reference/constAssertions.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,48 @@ tests/cases/conformance/expressions/typeAssertions/constAssertions.ts(63,10): er
let e3 = id(1) as const; // Error
~~~~~
!!! error TS1355: A 'const' assertions can only be applied to references to enum members, or string, number, boolean, array, or object literals.


let t1 = 'foo' as const;
let t2 = 'bar' as const;
let t3 = `${t1}-${t2}` as const;
let t4 = `${`(${t1})`}-${`(${t2})`}` as const;

function ff1(x: 'foo' | 'bar', y: 1 | 2) {
return `${x}-${y}` as const;
}

function ff2<T extends string, U extends string>(x: T, y: U) {
return `${x}-${y}` as const;
}

const ts1 = ff2('foo', 'bar');
const ts2 = ff2('foo', !!true ? '0' : '1');
const ts3 = ff2(!!true ? 'top' : 'bottom', !!true ? 'left' : 'right');

function ff3(x: 'foo' | 'bar', y: object) {
return `${x}${y}` as const;
}

type Action = "verify" | "write";
type ContentMatch = "match" | "nonMatch";
type Outcome = `${Action}_${ContentMatch}`;

function ff4(verify: boolean, contentMatches: boolean) {
const action : Action = verify ? `verify` : `write`;
const contentMatch: ContentMatch = contentMatches ? `match` : `nonMatch`;
const outcome: Outcome = `${action}_${contentMatch}` as const;
return outcome;
}

function ff5(verify: boolean, contentMatches: boolean) {
const action = verify ? `verify` : `write`;
const contentMatch = contentMatches ? `match` : `nonMatch`;
const outcome = `${action}_${contentMatch}` as const;
return outcome;
}

function accessorNames<S extends string>(propName: S) {
return [`get-${propName}`, `set-${propName}`] as const;
}

const ns1 = accessorNames('foo');
95 changes: 94 additions & 1 deletion tests/baselines/reference/constAssertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,51 @@ declare function id<T>(x: T): T;
let e1 = v1 as const; // Error
let e2 = (true ? 1 : 0) as const; // Error
let e3 = id(1) as const; // Error


let t1 = 'foo' as const;
let t2 = 'bar' as const;
let t3 = `${t1}-${t2}` as const;
let t4 = `${`(${t1})`}-${`(${t2})`}` as const;

function ff1(x: 'foo' | 'bar', y: 1 | 2) {
return `${x}-${y}` as const;
}

function ff2<T extends string, U extends string>(x: T, y: U) {
return `${x}-${y}` as const;
}

const ts1 = ff2('foo', 'bar');
const ts2 = ff2('foo', !!true ? '0' : '1');
const ts3 = ff2(!!true ? 'top' : 'bottom', !!true ? 'left' : 'right');

function ff3(x: 'foo' | 'bar', y: object) {
return `${x}${y}` as const;
}

type Action = "verify" | "write";
type ContentMatch = "match" | "nonMatch";
type Outcome = `${Action}_${ContentMatch}`;

function ff4(verify: boolean, contentMatches: boolean) {
const action : Action = verify ? `verify` : `write`;
const contentMatch: ContentMatch = contentMatches ? `match` : `nonMatch`;
const outcome: Outcome = `${action}_${contentMatch}` as const;
return outcome;
}

function ff5(verify: boolean, contentMatches: boolean) {
const action = verify ? `verify` : `write`;
const contentMatch = contentMatches ? `match` : `nonMatch`;
const outcome = `${action}_${contentMatch}` as const;
return outcome;
}

function accessorNames<S extends string>(propName: S) {
return [`get-${propName}`, `set-${propName}`] as const;
}

const ns1 = accessorNames('foo');

//// [constAssertions.js]
"use strict";
Expand Down Expand Up @@ -117,6 +161,38 @@ let q5 = { x: 10, y: 20 };
let e1 = v1; // Error
let e2 = (true ? 1 : 0); // Error
let e3 = id(1); // Error
let t1 = 'foo';
let t2 = 'bar';
let t3 = `${t1}-${t2}`;
let t4 = `${`(${t1})`}-${`(${t2})`}`;
function ff1(x, y) {
return `${x}-${y}`;
}
function ff2(x, y) {
return `${x}-${y}`;
}
const ts1 = ff2('foo', 'bar');
const ts2 = ff2('foo', !!true ? '0' : '1');
const ts3 = ff2(!!true ? 'top' : 'bottom', !!true ? 'left' : 'right');
function ff3(x, y) {
return `${x}${y}`;
}
function ff4(verify, contentMatches) {
const action = verify ? `verify` : `write`;
const contentMatch = contentMatches ? `match` : `nonMatch`;
const outcome = `${action}_${contentMatch}`;
return outcome;
}
function ff5(verify, contentMatches) {
const action = verify ? `verify` : `write`;
const contentMatch = contentMatches ? `match` : `nonMatch`;
const outcome = `${action}_${contentMatch}`;
return outcome;
}
function accessorNames(propName) {
return [`get-${propName}`, `set-${propName}`];
}
const ns1 = accessorNames('foo');


//// [constAssertions.d.ts]
Expand Down Expand Up @@ -218,3 +294,20 @@ declare function id<T>(x: T): T;
declare let e1: "abc";
declare let e2: 0 | 1;
declare let e3: 1;
declare let t1: "foo";
declare let t2: "bar";
declare let t3: "foo-bar";
declare let t4: "(foo)-(bar)";
declare function ff1(x: 'foo' | 'bar', y: 1 | 2): "foo-1" | "foo-2" | "bar-1" | "bar-2";
declare function ff2<T extends string, U extends string>(x: T, y: U): `${T}-${U}`;
declare const ts1: "foo-bar";
declare const ts2: "foo-1" | "foo-0";
declare const ts3: "top-left" | "top-right" | "bottom-left" | "bottom-right";
declare function ff3(x: 'foo' | 'bar', y: object): string;
declare type Action = "verify" | "write";
declare type ContentMatch = "match" | "nonMatch";
declare type Outcome = `${Action}_${ContentMatch}`;
declare function ff4(verify: boolean, contentMatches: boolean): "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch";
declare function ff5(verify: boolean, contentMatches: boolean): "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch";
declare function accessorNames<S extends string>(propName: S): readonly [`get-${S}`, `set-${S}`];
declare const ns1: readonly ["get-foo", "set-foo"];
135 changes: 135 additions & 0 deletions tests/baselines/reference/constAssertions.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,138 @@ let e3 = id(1) as const; // Error
>e3 : Symbol(e3, Decl(constAssertions.ts, 62, 3))
>id : Symbol(id, Decl(constAssertions.ts, 56, 34))

let t1 = 'foo' as const;
>t1 : Symbol(t1, Decl(constAssertions.ts, 64, 3))

let t2 = 'bar' as const;
>t2 : Symbol(t2, Decl(constAssertions.ts, 65, 3))

let t3 = `${t1}-${t2}` as const;
>t3 : Symbol(t3, Decl(constAssertions.ts, 66, 3))
>t1 : Symbol(t1, Decl(constAssertions.ts, 64, 3))
>t2 : Symbol(t2, Decl(constAssertions.ts, 65, 3))

let t4 = `${`(${t1})`}-${`(${t2})`}` as const;
>t4 : Symbol(t4, Decl(constAssertions.ts, 67, 3))
>t1 : Symbol(t1, Decl(constAssertions.ts, 64, 3))
>t2 : Symbol(t2, Decl(constAssertions.ts, 65, 3))

function ff1(x: 'foo' | 'bar', y: 1 | 2) {
>ff1 : Symbol(ff1, Decl(constAssertions.ts, 67, 46))
>x : Symbol(x, Decl(constAssertions.ts, 69, 13))
>y : Symbol(y, Decl(constAssertions.ts, 69, 30))

return `${x}-${y}` as const;
>x : Symbol(x, Decl(constAssertions.ts, 69, 13))
>y : Symbol(y, Decl(constAssertions.ts, 69, 30))
}

function ff2<T extends string, U extends string>(x: T, y: U) {
>ff2 : Symbol(ff2, Decl(constAssertions.ts, 71, 1))
>T : Symbol(T, Decl(constAssertions.ts, 73, 13))
>U : Symbol(U, Decl(constAssertions.ts, 73, 30))
>x : Symbol(x, Decl(constAssertions.ts, 73, 49))
>T : Symbol(T, Decl(constAssertions.ts, 73, 13))
>y : Symbol(y, Decl(constAssertions.ts, 73, 54))
>U : Symbol(U, Decl(constAssertions.ts, 73, 30))

return `${x}-${y}` as const;
>x : Symbol(x, Decl(constAssertions.ts, 73, 49))
>y : Symbol(y, Decl(constAssertions.ts, 73, 54))
}

const ts1 = ff2('foo', 'bar');
>ts1 : Symbol(ts1, Decl(constAssertions.ts, 77, 5))
>ff2 : Symbol(ff2, Decl(constAssertions.ts, 71, 1))

const ts2 = ff2('foo', !!true ? '0' : '1');
>ts2 : Symbol(ts2, Decl(constAssertions.ts, 78, 5))
>ff2 : Symbol(ff2, Decl(constAssertions.ts, 71, 1))

const ts3 = ff2(!!true ? 'top' : 'bottom', !!true ? 'left' : 'right');
>ts3 : Symbol(ts3, Decl(constAssertions.ts, 79, 5))
>ff2 : Symbol(ff2, Decl(constAssertions.ts, 71, 1))

function ff3(x: 'foo' | 'bar', y: object) {
>ff3 : Symbol(ff3, Decl(constAssertions.ts, 79, 70))
>x : Symbol(x, Decl(constAssertions.ts, 81, 13))
>y : Symbol(y, Decl(constAssertions.ts, 81, 30))

return `${x}${y}` as const;
>x : Symbol(x, Decl(constAssertions.ts, 81, 13))
>y : Symbol(y, Decl(constAssertions.ts, 81, 30))
}

type Action = "verify" | "write";
>Action : Symbol(Action, Decl(constAssertions.ts, 83, 1))

type ContentMatch = "match" | "nonMatch";
>ContentMatch : Symbol(ContentMatch, Decl(constAssertions.ts, 85, 33))

type Outcome = `${Action}_${ContentMatch}`;
>Outcome : Symbol(Outcome, Decl(constAssertions.ts, 86, 41))
>Action : Symbol(Action, Decl(constAssertions.ts, 83, 1))
>ContentMatch : Symbol(ContentMatch, Decl(constAssertions.ts, 85, 33))

function ff4(verify: boolean, contentMatches: boolean) {
>ff4 : Symbol(ff4, Decl(constAssertions.ts, 87, 43))
>verify : Symbol(verify, Decl(constAssertions.ts, 89, 13))
>contentMatches : Symbol(contentMatches, Decl(constAssertions.ts, 89, 29))

const action : Action = verify ? `verify` : `write`;
>action : Symbol(action, Decl(constAssertions.ts, 90, 9))
>Action : Symbol(Action, Decl(constAssertions.ts, 83, 1))
>verify : Symbol(verify, Decl(constAssertions.ts, 89, 13))

const contentMatch: ContentMatch = contentMatches ? `match` : `nonMatch`;
>contentMatch : Symbol(contentMatch, Decl(constAssertions.ts, 91, 9))
>ContentMatch : Symbol(ContentMatch, Decl(constAssertions.ts, 85, 33))
>contentMatches : Symbol(contentMatches, Decl(constAssertions.ts, 89, 29))

const outcome: Outcome = `${action}_${contentMatch}` as const;
>outcome : Symbol(outcome, Decl(constAssertions.ts, 92, 9))
>Outcome : Symbol(Outcome, Decl(constAssertions.ts, 86, 41))
>action : Symbol(action, Decl(constAssertions.ts, 90, 9))
>contentMatch : Symbol(contentMatch, Decl(constAssertions.ts, 91, 9))

return outcome;
>outcome : Symbol(outcome, Decl(constAssertions.ts, 92, 9))
}

function ff5(verify: boolean, contentMatches: boolean) {
>ff5 : Symbol(ff5, Decl(constAssertions.ts, 94, 1))
>verify : Symbol(verify, Decl(constAssertions.ts, 96, 13))
>contentMatches : Symbol(contentMatches, Decl(constAssertions.ts, 96, 29))

const action = verify ? `verify` : `write`;
>action : Symbol(action, Decl(constAssertions.ts, 97, 9))
>verify : Symbol(verify, Decl(constAssertions.ts, 96, 13))

const contentMatch = contentMatches ? `match` : `nonMatch`;
>contentMatch : Symbol(contentMatch, Decl(constAssertions.ts, 98, 9))
>contentMatches : Symbol(contentMatches, Decl(constAssertions.ts, 96, 29))

const outcome = `${action}_${contentMatch}` as const;
>outcome : Symbol(outcome, Decl(constAssertions.ts, 99, 9))
>action : Symbol(action, Decl(constAssertions.ts, 97, 9))
>contentMatch : Symbol(contentMatch, Decl(constAssertions.ts, 98, 9))

return outcome;
>outcome : Symbol(outcome, Decl(constAssertions.ts, 99, 9))
}

function accessorNames<S extends string>(propName: S) {
>accessorNames : Symbol(accessorNames, Decl(constAssertions.ts, 101, 1))
>S : Symbol(S, Decl(constAssertions.ts, 103, 23))
>propName : Symbol(propName, Decl(constAssertions.ts, 103, 41))
>S : Symbol(S, Decl(constAssertions.ts, 103, 23))

return [`get-${propName}`, `set-${propName}`] as const;
>propName : Symbol(propName, Decl(constAssertions.ts, 103, 41))
>propName : Symbol(propName, Decl(constAssertions.ts, 103, 41))
}

const ns1 = accessorNames('foo');
>ns1 : Symbol(ns1, Decl(constAssertions.ts, 107, 5))
>accessorNames : Symbol(accessorNames, Decl(constAssertions.ts, 101, 1))

Loading