Skip to content

Commit 164dddc

Browse files
feat(7481): Operator to ensure an expression is contextually typed by, and satisfies, some type (#46827)
* feat(7481): add explicit type compatibility check with 'satisfies' expression * Add failing test for lack of intersectioned contextual type * Implement the behavior * Add test corresponding to the 'if' * Add test based on defined scenarios * remove isExpression in favor of using type casting * move tests from compiler to conformance folder * update baseline * add missing contextFlags argument * use asserted type * accept baseline Co-authored-by: Ryan Cavanaugh <[email protected]>
1 parent 0715791 commit 164dddc

File tree

102 files changed

+3172
-449
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+3172
-449
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27506,6 +27506,8 @@ namespace ts {
2750627506
}
2750727507
case SyntaxKind.NonNullExpression:
2750827508
return getContextualType(parent as NonNullExpression, contextFlags);
27509+
case SyntaxKind.SatisfiesExpression:
27510+
return getTypeFromTypeNode((parent as SatisfiesExpression).type);
2750927511
case SyntaxKind.ExportAssignment:
2751027512
return tryGetTypeFromEffectiveTypeNode(parent as ExportAssignment);
2751127513
case SyntaxKind.JsxExpression:
@@ -32442,6 +32444,20 @@ namespace ts {
3244232444
}
3244332445
}
3244432446

32447+
function checkSatisfiesExpression(node: SatisfiesExpression) {
32448+
checkSourceElement(node.type);
32449+
32450+
const targetType = getTypeFromTypeNode(node.type);
32451+
if (isErrorType(targetType)) {
32452+
return targetType;
32453+
}
32454+
32455+
const exprType = checkExpression(node.expression);
32456+
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
32457+
32458+
return exprType;
32459+
}
32460+
3244532461
function checkMetaProperty(node: MetaProperty): Type {
3244632462
checkGrammarMetaProperty(node);
3244732463

@@ -35235,6 +35251,8 @@ namespace ts {
3523535251
return checkNonNullAssertion(node as NonNullExpression);
3523635252
case SyntaxKind.ExpressionWithTypeArguments:
3523735253
return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments);
35254+
case SyntaxKind.SatisfiesExpression:
35255+
return checkSatisfiesExpression(node as SatisfiesExpression);
3523835256
case SyntaxKind.MetaProperty:
3523935257
return checkMetaProperty(node as MetaProperty);
3524035258
case SyntaxKind.DeleteExpression:

src/compiler/diagnosticMessages.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,7 +1100,7 @@
11001100
"category": "Error",
11011101
"code": 1359
11021102
},
1103-
"Class constructor may not be a generator.": {
1103+
"Type '{0}' does not satisfy the expected type '{1}'.": {
11041104
"category": "Error",
11051105
"code": 1360
11061106
},
@@ -1132,6 +1132,10 @@
11321132
"category": "Message",
11331133
"code": 1367
11341134
},
1135+
"Class constructor may not be a generator.": {
1136+
"category": "Error",
1137+
"code": 1368
1138+
},
11351139
"Did you mean '{0}'?": {
11361140
"category": "Message",
11371141
"code": 1369
@@ -6376,6 +6380,10 @@
63766380
"category": "Error",
63776381
"code": 8036
63786382
},
6383+
"Type satisfaction expressions can only be used in TypeScript files.": {
6384+
"category": "Error",
6385+
"code": 8037
6386+
},
63796387

63806388
"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
63816389
"category": "Error",

src/compiler/emitter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,8 @@ namespace ts {
17631763
return emitNonNullExpression(node as NonNullExpression);
17641764
case SyntaxKind.ExpressionWithTypeArguments:
17651765
return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments);
1766+
case SyntaxKind.SatisfiesExpression:
1767+
return emitSatisfiesExpression(node as SatisfiesExpression);
17661768
case SyntaxKind.MetaProperty:
17671769
return emitMetaProperty(node as MetaProperty);
17681770
case SyntaxKind.SyntheticExpression:
@@ -2846,6 +2848,16 @@ namespace ts {
28462848
writeOperator("!");
28472849
}
28482850

2851+
function emitSatisfiesExpression(node: SatisfiesExpression) {
2852+
emitExpression(node.expression, /*parenthesizerRules*/ undefined);
2853+
if (node.type) {
2854+
writeSpace();
2855+
writeKeyword("satisfies");
2856+
writeSpace();
2857+
emit(node.type);
2858+
}
2859+
}
2860+
28492861
function emitMetaProperty(node: MetaProperty) {
28502862
writeToken(node.keywordToken, node.pos, writePunctuation);
28512863
writePunctuation(".");

src/compiler/factory/nodeFactory.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ namespace ts {
222222
updateAsExpression,
223223
createNonNullExpression,
224224
updateNonNullExpression,
225+
createSatisfiesExpression,
226+
updateSatisfiesExpression,
225227
createNonNullChain,
226228
updateNonNullChain,
227229
createMetaProperty,
@@ -3142,6 +3144,26 @@ namespace ts {
31423144
: node;
31433145
}
31443146

3147+
// @api
3148+
function createSatisfiesExpression(expression: Expression, type: TypeNode) {
3149+
const node = createBaseExpression<SatisfiesExpression>(SyntaxKind.SatisfiesExpression);
3150+
node.expression = expression;
3151+
node.type = type;
3152+
node.transformFlags |=
3153+
propagateChildFlags(node.expression) |
3154+
propagateChildFlags(node.type) |
3155+
TransformFlags.ContainsTypeScript;
3156+
return node;
3157+
}
3158+
3159+
// @api
3160+
function updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode) {
3161+
return node.expression !== expression
3162+
|| node.type !== type
3163+
? update(createSatisfiesExpression(expression, type), node)
3164+
: node;
3165+
}
3166+
31453167
// @api
31463168
function createNonNullChain(expression: Expression) {
31473169
const node = createBaseExpression<NonNullChain>(SyntaxKind.NonNullExpression);
@@ -5730,6 +5752,7 @@ namespace ts {
57305752
case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression);
57315753
case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression);
57325754
case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type);
5755+
case SyntaxKind.SatisfiesExpression: return updateSatisfiesExpression(outerExpression, expression, outerExpression.type);
57335756
case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression);
57345757
case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression);
57355758
}
@@ -6465,6 +6488,7 @@ namespace ts {
64656488
case SyntaxKind.ArrayBindingPattern:
64666489
return TransformFlags.BindingPatternExcludes;
64676490
case SyntaxKind.TypeAssertionExpression:
6491+
case SyntaxKind.SatisfiesExpression:
64686492
case SyntaxKind.AsExpression:
64696493
case SyntaxKind.PartiallyEmittedExpression:
64706494
case SyntaxKind.ParenthesizedExpression:

src/compiler/factory/nodeTests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ namespace ts {
438438
return node.kind === SyntaxKind.AsExpression;
439439
}
440440

441+
export function isSatisfiesExpression(node: Node): node is SatisfiesExpression {
442+
return node.kind === SyntaxKind.SatisfiesExpression;
443+
}
444+
441445
export function isNonNullExpression(node: Node): node is NonNullExpression {
442446
return node.kind === SyntaxKind.NonNullExpression;
443447
}

src/compiler/factory/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ namespace ts {
437437
return (kinds & OuterExpressionKinds.Parentheses) !== 0;
438438
case SyntaxKind.TypeAssertionExpression:
439439
case SyntaxKind.AsExpression:
440+
case SyntaxKind.SatisfiesExpression:
440441
return (kinds & OuterExpressionKinds.TypeAssertions) !== 0;
441442
case SyntaxKind.NonNullExpression:
442443
return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0;

src/compiler/parser.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ namespace ts {
393393
[SyntaxKind.NonNullExpression]: function forEachChildInNonNullExpression<T>(node: NonNullExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
394394
return visitNode(cbNode, node.expression);
395395
},
396+
[SyntaxKind.SatisfiesExpression]: function forEachChildInSatisfiesExpression<T>(node: SatisfiesExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
397+
return visitNode(cbNode, node.expression) || visitNode(cbNode, node.type);
398+
},
396399
[SyntaxKind.MetaProperty]: function forEachChildInMetaProperty<T>(node: MetaProperty, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
397400
return visitNode(cbNode, node.name);
398401
},
@@ -841,7 +844,6 @@ namespace ts {
841844
if (node === undefined || node.kind <= SyntaxKind.LastToken) {
842845
return;
843846
}
844-
845847
const fn = (forEachChildTable as Record<SyntaxKind, ForEachChildFunction<any>>)[node.kind];
846848
return fn === undefined ? undefined : fn(node, cbNode, cbNodes);
847849
}
@@ -5109,7 +5111,7 @@ namespace ts {
51095111
break;
51105112
}
51115113

5112-
if (token() === SyntaxKind.AsKeyword) {
5114+
if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) {
51135115
// Make sure we *do* perform ASI for constructs like this:
51145116
// var x = foo
51155117
// as (Bar)
@@ -5119,8 +5121,10 @@ namespace ts {
51195121
break;
51205122
}
51215123
else {
5124+
const keywordKind = token();
51225125
nextToken();
5123-
leftOperand = makeAsExpression(leftOperand, parseType());
5126+
leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) :
5127+
makeAsExpression(leftOperand, parseType());
51245128
}
51255129
}
51265130
else {
@@ -5139,6 +5143,10 @@ namespace ts {
51395143
return getBinaryOperatorPrecedence(token()) > 0;
51405144
}
51415145

5146+
function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression {
5147+
return finishNode(factory.createSatisfiesExpression(left, right), left.pos);
5148+
}
5149+
51425150
function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression {
51435151
return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos);
51445152
}

src/compiler/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2372,6 +2372,9 @@ namespace ts {
23722372
case SyntaxKind.AsExpression:
23732373
diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files));
23742374
return "skip";
2375+
case SyntaxKind.SatisfiesExpression:
2376+
diagnostics.push(createDiagnosticForNode((node as SatisfiesExpression).type, Diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files));
2377+
return "skip";
23752378
case SyntaxKind.TypeAssertionExpression:
23762379
Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX.
23772380
}

src/compiler/scanner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ namespace ts {
136136
require: SyntaxKind.RequireKeyword,
137137
global: SyntaxKind.GlobalKeyword,
138138
return: SyntaxKind.ReturnKeyword,
139+
satisfies: SyntaxKind.SatisfiesKeyword,
139140
set: SyntaxKind.SetKeyword,
140141
static: SyntaxKind.StaticKeyword,
141142
string: SyntaxKind.StringKeyword,

src/compiler/transformers/ts.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@ namespace ts {
529529
// TypeScript type assertions are removed, but their subtrees are preserved.
530530
return visitAssertionExpression(node as AssertionExpression);
531531

532+
case SyntaxKind.SatisfiesExpression:
533+
return visitSatisfiesExpression(node as SatisfiesExpression);
534+
532535
case SyntaxKind.CallExpression:
533536
return visitCallExpression(node as CallExpression);
534537

@@ -1421,6 +1424,11 @@ namespace ts {
14211424
return factory.createPartiallyEmittedExpression(expression, node);
14221425
}
14231426

1427+
function visitSatisfiesExpression(node: SatisfiesExpression): Expression {
1428+
const expression = visitNode(node.expression, visitor, isExpression);
1429+
return factory.createPartiallyEmittedExpression(expression, node);
1430+
}
1431+
14241432
function visitCallExpression(node: CallExpression) {
14251433
return factory.updateCallExpression(
14261434
node,

src/compiler/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ namespace ts {
181181
RequireKeyword,
182182
NumberKeyword,
183183
ObjectKeyword,
184+
SatisfiesKeyword,
184185
SetKeyword,
185186
StringKeyword,
186187
SymbolKeyword,
@@ -274,6 +275,7 @@ namespace ts {
274275
NonNullExpression,
275276
MetaProperty,
276277
SyntheticExpression,
278+
SatisfiesExpression,
277279

278280
// Misc
279281
TemplateSpan,
@@ -607,6 +609,7 @@ namespace ts {
607609
| SyntaxKind.OverrideKeyword
608610
| SyntaxKind.RequireKeyword
609611
| SyntaxKind.ReturnKeyword
612+
| SyntaxKind.SatisfiesKeyword
610613
| SyntaxKind.SetKeyword
611614
| SyntaxKind.StaticKeyword
612615
| SyntaxKind.StringKeyword
@@ -1007,6 +1010,7 @@ namespace ts {
10071010
| ExpressionWithTypeArguments
10081011
| AsExpression
10091012
| NonNullExpression
1013+
| SatisfiesExpression
10101014
| MetaProperty
10111015
| TemplateSpan
10121016
| Block
@@ -2815,6 +2819,12 @@ namespace ts {
28152819
readonly expression: UnaryExpression;
28162820
}
28172821

2822+
export interface SatisfiesExpression extends Expression {
2823+
readonly kind: SyntaxKind.SatisfiesExpression;
2824+
readonly expression: Expression;
2825+
readonly type: TypeNode;
2826+
}
2827+
28182828
export type AssertionExpression =
28192829
| TypeAssertion
28202830
| AsExpression
@@ -7520,6 +7530,7 @@ namespace ts {
75207530
export type OuterExpression =
75217531
| ParenthesizedExpression
75227532
| TypeAssertion
7533+
| SatisfiesExpression
75237534
| AsExpression
75247535
| NonNullExpression
75257536
| PartiallyEmittedExpression;
@@ -7856,6 +7867,8 @@ namespace ts {
78567867
updateNonNullChain(node: NonNullChain, expression: Expression): NonNullChain;
78577868
createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty;
78587869
updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty;
7870+
createSatisfiesExpression(expression: Expression, type: TypeNode): SatisfiesExpression;
7871+
updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode): SatisfiesExpression;
78597872

78607873
//
78617874
// Misc

src/compiler/utilities.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,7 @@ namespace ts {
19951995
case SyntaxKind.TaggedTemplateExpression:
19961996
case SyntaxKind.AsExpression:
19971997
case SyntaxKind.TypeAssertionExpression:
1998+
case SyntaxKind.SatisfiesExpression:
19981999
case SyntaxKind.NonNullExpression:
19992000
case SyntaxKind.ParenthesizedExpression:
20002001
case SyntaxKind.FunctionExpression:
@@ -2095,6 +2096,8 @@ namespace ts {
20952096
return (parent as ExpressionWithTypeArguments).expression === node && !isPartOfTypeNode(parent);
20962097
case SyntaxKind.ShorthandPropertyAssignment:
20972098
return (parent as ShorthandPropertyAssignment).objectAssignmentInitializer === node;
2099+
case SyntaxKind.SatisfiesExpression:
2100+
return node === (parent as SatisfiesExpression).expression;
20982101
default:
20992102
return isExpressionNode(parent);
21002103
}
@@ -3801,6 +3804,7 @@ namespace ts {
38013804
return OperatorPrecedence.Member;
38023805

38033806
case SyntaxKind.AsExpression:
3807+
case SyntaxKind.SatisfiesExpression:
38043808
return OperatorPrecedence.Relational;
38053809

38063810
case SyntaxKind.ThisKeyword:
@@ -3859,6 +3863,7 @@ namespace ts {
38593863
case SyntaxKind.InstanceOfKeyword:
38603864
case SyntaxKind.InKeyword:
38613865
case SyntaxKind.AsKeyword:
3866+
case SyntaxKind.SatisfiesKeyword:
38623867
return OperatorPrecedence.Relational;
38633868
case SyntaxKind.LessThanLessThanToken:
38643869
case SyntaxKind.GreaterThanGreaterThanToken:
@@ -5930,7 +5935,8 @@ namespace ts {
59305935
case SyntaxKind.PropertyAccessExpression:
59315936
case SyntaxKind.NonNullExpression:
59325937
case SyntaxKind.PartiallyEmittedExpression:
5933-
node = (node as CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression).expression;
5938+
case SyntaxKind.SatisfiesExpression:
5939+
node = (node as CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression | SatisfiesExpression).expression;
59345940
continue;
59355941
}
59365942

src/compiler/utilitiesPublic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,6 +1642,7 @@ namespace ts {
16421642
case SyntaxKind.OmittedExpression:
16431643
case SyntaxKind.CommaListExpression:
16441644
case SyntaxKind.PartiallyEmittedExpression:
1645+
case SyntaxKind.SatisfiesExpression:
16451646
return true;
16461647
default:
16471648
return isUnaryExpressionKind(kind);

src/compiler/visitorPublic.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,13 +888,19 @@ namespace ts {
888888
nodeVisitor(node.type, visitor, isTypeNode));
889889
},
890890

891+
[SyntaxKind.SatisfiesExpression]: function visitEachChildOfSatisfiesExpression(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) {
892+
return context.factory.updateSatisfiesExpression(node,
893+
nodeVisitor(node.expression, visitor, isExpression),
894+
nodeVisitor(node.type, visitor, isTypeNode));
895+
},
896+
891897
[SyntaxKind.NonNullExpression]: function visitEachChildOfNonNullExpression(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) {
892898
return isOptionalChain(node) ?
893899
context.factory.updateNonNullChain(node,
894900
nodeVisitor(node.expression, visitor, isExpression)) :
895901
context.factory.updateNonNullExpression(node,
896902
nodeVisitor(node.expression, visitor, isExpression));
897-
},
903+
},
898904

899905
[SyntaxKind.MetaProperty]: function visitEachChildOfMetaProperty(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) {
900906
return context.factory.updateMetaProperty(node,

0 commit comments

Comments
 (0)