Const contexts for template literals (#40707)

* Support const assertions with template literal expressions

* Add tests

* Accept new baselines
This commit is contained in:
Anders Hejlsberg 2020-09-22 13:11:17 -10:00 committed by GitHub
parent c5a28fcdec
commit 5d6cce5ca7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 517 additions and 14 deletions

View file

@ -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);
@ -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 {
@ -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 {

View file

@ -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');

View file

@ -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";
@ -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]
@ -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"];

View file

@ -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))

View file

@ -354,3 +354,189 @@ let e3 = id(1) as const; // Error
>id : <T>(x: T) => T
>1 : 1
let t1 = 'foo' as const;
>t1 : "foo"
>'foo' as const : "foo"
>'foo' : "foo"
let t2 = 'bar' as const;
>t2 : "bar"
>'bar' as const : "bar"
>'bar' : "bar"
let t3 = `${t1}-${t2}` as const;
>t3 : "foo-bar"
>`${t1}-${t2}` as const : "foo-bar"
>`${t1}-${t2}` : "foo-bar"
>t1 : "foo"
>t2 : "bar"
let t4 = `${`(${t1})`}-${`(${t2})`}` as const;
>t4 : "(foo)-(bar)"
>`${`(${t1})`}-${`(${t2})`}` as const : "(foo)-(bar)"
>`${`(${t1})`}-${`(${t2})`}` : "(foo)-(bar)"
>`(${t1})` : "(foo)"
>t1 : "foo"
>`(${t2})` : "(bar)"
>t2 : "bar"
function ff1(x: 'foo' | 'bar', y: 1 | 2) {
>ff1 : (x: 'foo' | 'bar', y: 1 | 2) => "foo-1" | "foo-2" | "bar-1" | "bar-2"
>x : "foo" | "bar"
>y : 1 | 2
return `${x}-${y}` as const;
>`${x}-${y}` as const : "foo-1" | "foo-2" | "bar-1" | "bar-2"
>`${x}-${y}` : "foo-1" | "foo-2" | "bar-1" | "bar-2"
>x : "foo" | "bar"
>y : 1 | 2
}
function ff2<T extends string, U extends string>(x: T, y: U) {
>ff2 : <T extends string, U extends string>(x: T, y: U) => `${T}-${U}`
>x : T
>y : U
return `${x}-${y}` as const;
>`${x}-${y}` as const : `${T}-${U}`
>`${x}-${y}` : `${T}-${U}`
>x : T
>y : U
}
const ts1 = ff2('foo', 'bar');
>ts1 : "foo-bar"
>ff2('foo', 'bar') : "foo-bar"
>ff2 : <T extends string, U extends string>(x: T, y: U) => `${T}-${U}`
>'foo' : "foo"
>'bar' : "bar"
const ts2 = ff2('foo', !!true ? '0' : '1');
>ts2 : "foo-1" | "foo-0"
>ff2('foo', !!true ? '0' : '1') : "foo-1" | "foo-0"
>ff2 : <T extends string, U extends string>(x: T, y: U) => `${T}-${U}`
>'foo' : "foo"
>!!true ? '0' : '1' : "0" | "1"
>!!true : true
>!true : false
>true : true
>'0' : "0"
>'1' : "1"
const ts3 = ff2(!!true ? 'top' : 'bottom', !!true ? 'left' : 'right');
>ts3 : "top-left" | "top-right" | "bottom-left" | "bottom-right"
>ff2(!!true ? 'top' : 'bottom', !!true ? 'left' : 'right') : "top-left" | "top-right" | "bottom-left" | "bottom-right"
>ff2 : <T extends string, U extends string>(x: T, y: U) => `${T}-${U}`
>!!true ? 'top' : 'bottom' : "top" | "bottom"
>!!true : true
>!true : false
>true : true
>'top' : "top"
>'bottom' : "bottom"
>!!true ? 'left' : 'right' : "left" | "right"
>!!true : true
>!true : false
>true : true
>'left' : "left"
>'right' : "right"
function ff3(x: 'foo' | 'bar', y: object) {
>ff3 : (x: 'foo' | 'bar', y: object) => string
>x : "foo" | "bar"
>y : object
return `${x}${y}` as const;
>`${x}${y}` as const : string
>`${x}${y}` : string
>x : "foo" | "bar"
>y : object
}
type Action = "verify" | "write";
>Action : Action
type ContentMatch = "match" | "nonMatch";
>ContentMatch : ContentMatch
type Outcome = `${Action}_${ContentMatch}`;
>Outcome : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
function ff4(verify: boolean, contentMatches: boolean) {
>ff4 : (verify: boolean, contentMatches: boolean) => "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>verify : boolean
>contentMatches : boolean
const action : Action = verify ? `verify` : `write`;
>action : Action
>verify ? `verify` : `write` : Action
>verify : boolean
>`verify` : "verify"
>`write` : "write"
const contentMatch: ContentMatch = contentMatches ? `match` : `nonMatch`;
>contentMatch : ContentMatch
>contentMatches ? `match` : `nonMatch` : ContentMatch
>contentMatches : boolean
>`match` : "match"
>`nonMatch` : "nonMatch"
const outcome: Outcome = `${action}_${contentMatch}` as const;
>outcome : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>`${action}_${contentMatch}` as const : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>`${action}_${contentMatch}` : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>action : Action
>contentMatch : ContentMatch
return outcome;
>outcome : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
}
function ff5(verify: boolean, contentMatches: boolean) {
>ff5 : (verify: boolean, contentMatches: boolean) => "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>verify : boolean
>contentMatches : boolean
const action = verify ? `verify` : `write`;
>action : "verify" | "write"
>verify ? `verify` : `write` : Action
>verify : boolean
>`verify` : "verify"
>`write` : "write"
const contentMatch = contentMatches ? `match` : `nonMatch`;
>contentMatch : "match" | "nonMatch"
>contentMatches ? `match` : `nonMatch` : ContentMatch
>contentMatches : boolean
>`match` : "match"
>`nonMatch` : "nonMatch"
const outcome = `${action}_${contentMatch}` as const;
>outcome : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>`${action}_${contentMatch}` as const : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>`${action}_${contentMatch}` : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
>action : Action
>contentMatch : ContentMatch
return outcome;
>outcome : "verify_match" | "verify_nonMatch" | "write_match" | "write_nonMatch"
}
function accessorNames<S extends string>(propName: S) {
>accessorNames : <S extends string>(propName: S) => readonly [`get-${S}`, `set-${S}`]
>propName : S
return [`get-${propName}`, `set-${propName}`] as const;
>[`get-${propName}`, `set-${propName}`] as const : readonly [`get-${S}`, `set-${S}`]
>[`get-${propName}`, `set-${propName}`] : readonly [`get-${S}`, `set-${S}`]
>`get-${propName}` : `get-${S}`
>propName : S
>`set-${propName}` : `set-${S}`
>propName : S
}
const ns1 = accessorNames('foo');
>ns1 : readonly ["get-foo", "set-foo"]
>accessorNames('foo') : readonly ["get-foo", "set-foo"]
>accessorNames : <S extends string>(propName: S) => readonly [`get-${S}`, `set-${S}`]
>'foo' : "foo"

View file

@ -65,3 +65,48 @@ 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');