Skip to content

Commit 37dd828

Browse files
committed
feat(7481): add explicit type compatibility check with 'satisfies' expression
1 parent 719ab0b commit 37dd828

38 files changed

+1149
-442
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31313,6 +31313,22 @@ namespace ts {
3131331313
getNonNullableType(checkExpression(node.expression));
3131431314
}
3131531315

31316+
function checkSatisfiesExpression(node: SatisfiesExpression) {
31317+
checkSourceElement(node.type);
31318+
31319+
const targetType = getTypeFromTypeNode(node.type);
31320+
if (isErrorType(targetType)) {
31321+
return targetType;
31322+
}
31323+
31324+
const exprType = getWidenedType(checkExpression(node.expression));
31325+
if (!isTypeIdenticalTo(targetType, exprType)) {
31326+
error(node.type, Diagnostics._0_is_not_satisfied_1, typeToString(targetType), typeToString(exprType));
31327+
}
31328+
31329+
return targetType;
31330+
}
31331+
3131631332
function checkMetaProperty(node: MetaProperty): Type {
3131731333
checkGrammarMetaProperty(node);
3131831334

@@ -34065,6 +34081,8 @@ namespace ts {
3406534081
return checkAssertion(node as AssertionExpression);
3406634082
case SyntaxKind.NonNullExpression:
3406734083
return checkNonNullAssertion(node as NonNullExpression);
34084+
case SyntaxKind.SatisfiesExpression:
34085+
return checkSatisfiesExpression(node as SatisfiesExpression);
3406834086
case SyntaxKind.MetaProperty:
3406934087
return checkMetaProperty(node as MetaProperty);
3407034088
case SyntaxKind.DeleteExpression:

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,10 @@
10601060
"category": "Error",
10611061
"code": 1359
10621062
},
1063+
"'{0}' is not satisfied '{1}'": {
1064+
"category": "Error",
1065+
"code": 1360
1066+
},
10631067
"'{0}' cannot be used as a value because it was imported using 'import type'.": {
10641068
"category": "Error",
10651069
"code": 1361
@@ -6151,6 +6155,10 @@
61516155
"category": "Error",
61526156
"code": 8034
61536157
},
6158+
"Type satisfaction expressions can only be used in TypeScript files.": {
6159+
"category": "Error",
6160+
"code": 8035
6161+
},
61546162
"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
61556163
"category": "Error",
61566164
"code": 9005

src/compiler/emitter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,8 @@ namespace ts {
17271727
return emitAsExpression(node as AsExpression);
17281728
case SyntaxKind.NonNullExpression:
17291729
return emitNonNullExpression(node as NonNullExpression);
1730+
case SyntaxKind.SatisfiesExpression:
1731+
return emitSatisfiesExpression(node as SatisfiesExpression);
17301732
case SyntaxKind.MetaProperty:
17311733
return emitMetaProperty(node as MetaProperty);
17321734
case SyntaxKind.SyntheticExpression:
@@ -2799,6 +2801,16 @@ namespace ts {
27992801
writeOperator("!");
28002802
}
28012803

2804+
function emitSatisfiesExpression(node: SatisfiesExpression) {
2805+
emitExpression(node.expression, /*parenthesizerRules*/ undefined);
2806+
if (node.type) {
2807+
writeSpace();
2808+
writeKeyword("satisfies");
2809+
writeSpace();
2810+
emit(node.type);
2811+
}
2812+
}
2813+
28022814
function emitMetaProperty(node: MetaProperty) {
28032815
writeToken(node.keywordToken, node.pos, writePunctuation);
28042816
writePunctuation(".");

src/compiler/factory/nodeFactory.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ namespace ts {
218218
updateAsExpression,
219219
createNonNullExpression,
220220
updateNonNullExpression,
221+
createSatisfiesExpression,
222+
updateSatisfiesExpression,
221223
createNonNullChain,
222224
updateNonNullChain,
223225
createMetaProperty,
@@ -3099,6 +3101,26 @@ namespace ts {
30993101
: node;
31003102
}
31013103

3104+
// @api
3105+
function createSatisfiesExpression(expression: Expression, type: TypeNode) {
3106+
const node = createBaseExpression<SatisfiesExpression>(SyntaxKind.SatisfiesExpression);
3107+
node.expression = expression;
3108+
node.type = type;
3109+
node.transformFlags |=
3110+
propagateChildFlags(node.expression) |
3111+
propagateChildFlags(node.type) |
3112+
TransformFlags.ContainsTypeScript;
3113+
return node;
3114+
}
3115+
3116+
// @api
3117+
function updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode) {
3118+
return node.expression !== expression
3119+
|| node.type !== type
3120+
? update(createSatisfiesExpression(expression, type), node)
3121+
: node;
3122+
}
3123+
31023124
// @api
31033125
function createNonNullChain(expression: Expression) {
31043126
const node = createBaseExpression<NonNullChain>(SyntaxKind.NonNullExpression);
@@ -5590,6 +5612,7 @@ namespace ts {
55905612
case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression);
55915613
case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression);
55925614
case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type);
5615+
case SyntaxKind.SatisfiesExpression: return updateSatisfiesExpression(outerExpression, expression, outerExpression.type);
55935616
case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression);
55945617
case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression);
55955618
}
@@ -6320,6 +6343,7 @@ namespace ts {
63206343
case SyntaxKind.ArrayBindingPattern:
63216344
return TransformFlags.BindingPatternExcludes;
63226345
case SyntaxKind.TypeAssertionExpression:
6346+
case SyntaxKind.SatisfiesExpression:
63236347
case SyntaxKind.AsExpression:
63246348
case SyntaxKind.PartiallyEmittedExpression:
63256349
case SyntaxKind.ParenthesizedExpression:

src/compiler/factory/nodeTests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@ namespace ts {
433433
return node.kind === SyntaxKind.AsExpression;
434434
}
435435

436+
export function isSatisfiesExpression(node: Node): node is SatisfiesExpression {
437+
return node.kind === SyntaxKind.SatisfiesExpression;
438+
}
439+
436440
export function isNonNullExpression(node: Node): node is NonNullExpression {
437441
return node.kind === SyntaxKind.NonNullExpression;
438442
}

src/compiler/factory/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ namespace ts {
442442
return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0;
443443
case SyntaxKind.PartiallyEmittedExpression:
444444
return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0;
445+
case SyntaxKind.SatisfiesExpression:
446+
return (kinds & OuterExpressionKinds.SatisfiesExpression) !== 0;
445447
}
446448
return false;
447449
}

src/compiler/parser.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ namespace ts {
276276
visitNode(cbNode, (node as AsExpression).type);
277277
case SyntaxKind.NonNullExpression:
278278
return visitNode(cbNode, (node as NonNullExpression).expression);
279+
case SyntaxKind.SatisfiesExpression:
280+
return visitNode(cbNode, (node as SatisfiesExpression).expression) ||
281+
visitNode(cbNode, (node as SatisfiesExpression).type);
279282
case SyntaxKind.MetaProperty:
280283
return visitNode(cbNode, (node as MetaProperty).name);
281284
case SyntaxKind.ConditionalExpression:
@@ -4670,7 +4673,7 @@ namespace ts {
46704673
break;
46714674
}
46724675

4673-
if (token() === SyntaxKind.AsKeyword) {
4676+
if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) {
46744677
// Make sure we *do* perform ASI for constructs like this:
46754678
// var x = foo
46764679
// as (Bar)
@@ -4680,8 +4683,10 @@ namespace ts {
46804683
break;
46814684
}
46824685
else {
4686+
const keywordKind = token();
46834687
nextToken();
4684-
leftOperand = makeAsExpression(leftOperand, parseType());
4688+
leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) :
4689+
makeAsExpression(leftOperand, parseType());
46854690
}
46864691
}
46874692
else {
@@ -4700,6 +4705,10 @@ namespace ts {
47004705
return getBinaryOperatorPrecedence(token()) > 0;
47014706
}
47024707

4708+
function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression {
4709+
return finishNode(factory.createSatisfiesExpression(left, right), left.pos);
4710+
}
4711+
47034712
function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression {
47044713
return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos);
47054714
}

src/compiler/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,6 +2168,9 @@ namespace ts {
21682168
case SyntaxKind.AsExpression:
21692169
diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files));
21702170
return "skip";
2171+
case SyntaxKind.SatisfiesExpression:
2172+
diagnostics.push(createDiagnosticForNode((node as SatisfiesExpression).type, Diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files));
2173+
return "skip";
21712174
case SyntaxKind.TypeAssertionExpression:
21722175
Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX.
21732176
}

src/compiler/scanner.ts

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

src/compiler/transformers/ts.ts

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

516+
case SyntaxKind.SatisfiesExpression:
517+
return visitSatisfiesExpression(node as SatisfiesExpression);
518+
516519
case SyntaxKind.CallExpression:
517520
return visitCallExpression(node as CallExpression);
518521

@@ -2258,6 +2261,11 @@ namespace ts {
22582261
return factory.createPartiallyEmittedExpression(expression, node);
22592262
}
22602263

2264+
function visitSatisfiesExpression(node: SatisfiesExpression): Expression {
2265+
const expression = visitNode(node.expression, visitor, isExpression);
2266+
return factory.createPartiallyEmittedExpression(expression, node);
2267+
}
2268+
22612269
function visitCallExpression(node: CallExpression) {
22622270
return factory.updateCallExpression(
22632271
node,

src/compiler/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ namespace ts {
180180
RequireKeyword,
181181
NumberKeyword,
182182
ObjectKeyword,
183+
SatisfiesKeyword,
183184
SetKeyword,
184185
StringKeyword,
185186
SymbolKeyword,
@@ -273,6 +274,7 @@ namespace ts {
273274
NonNullExpression,
274275
MetaProperty,
275276
SyntheticExpression,
277+
SatisfiesExpression,
276278

277279
// Misc
278280
TemplateSpan,
@@ -602,6 +604,7 @@ namespace ts {
602604
| SyntaxKind.OverrideKeyword
603605
| SyntaxKind.RequireKeyword
604606
| SyntaxKind.ReturnKeyword
607+
| SyntaxKind.SatisfiesKeyword
605608
| SyntaxKind.SetKeyword
606609
| SyntaxKind.StaticKeyword
607610
| SyntaxKind.StringKeyword
@@ -2485,6 +2488,12 @@ namespace ts {
24852488
readonly expression: UnaryExpression;
24862489
}
24872490

2491+
export interface SatisfiesExpression extends Expression {
2492+
readonly kind: SyntaxKind.SatisfiesExpression;
2493+
readonly expression: Expression;
2494+
readonly type: TypeNode;
2495+
}
2496+
24882497
export type AssertionExpression =
24892498
| TypeAssertion
24902499
| AsExpression
@@ -7023,17 +7032,19 @@ namespace ts {
70237032
TypeAssertions = 1 << 1,
70247033
NonNullAssertions = 1 << 2,
70257034
PartiallyEmittedExpressions = 1 << 3,
7035+
SatisfiesExpression = 1 << 4,
70267036

70277037
Assertions = TypeAssertions | NonNullAssertions,
70287038
All = Parentheses | Assertions | PartiallyEmittedExpressions,
70297039

7030-
ExcludeJSDocTypeAssertion = 1 << 4,
7040+
ExcludeJSDocTypeAssertion = 1 << 5,
70317041
}
70327042

70337043
/* @internal */
70347044
export type OuterExpression =
70357045
| ParenthesizedExpression
70367046
| TypeAssertion
7047+
| SatisfiesExpression
70377048
| AsExpression
70387049
| NonNullExpression
70397050
| PartiallyEmittedExpression;
@@ -7363,6 +7374,8 @@ namespace ts {
73637374
updateNonNullChain(node: NonNullChain, expression: Expression): NonNullChain;
73647375
createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty;
73657376
updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty;
7377+
createSatisfiesExpression(expression: Expression, type: TypeNode): SatisfiesExpression;
7378+
updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode): SatisfiesExpression;
73667379

73677380
//
73687381
// Misc

src/compiler/utilities.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,7 @@ namespace ts {
19361936
case SyntaxKind.TaggedTemplateExpression:
19371937
case SyntaxKind.AsExpression:
19381938
case SyntaxKind.TypeAssertionExpression:
1939+
case SyntaxKind.SatisfiesExpression:
19391940
case SyntaxKind.NonNullExpression:
19401941
case SyntaxKind.ParenthesizedExpression:
19411942
case SyntaxKind.FunctionExpression:
@@ -2034,6 +2035,8 @@ namespace ts {
20342035
return (parent as ExpressionWithTypeArguments).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent);
20352036
case SyntaxKind.ShorthandPropertyAssignment:
20362037
return (parent as ShorthandPropertyAssignment).objectAssignmentInitializer === node;
2038+
case SyntaxKind.SatisfiesExpression:
2039+
return node === (parent as SatisfiesExpression).expression;
20372040
default:
20382041
return isExpressionNode(parent);
20392042
}
@@ -3722,6 +3725,7 @@ namespace ts {
37223725
return OperatorPrecedence.Member;
37233726

37243727
case SyntaxKind.AsExpression:
3728+
case SyntaxKind.SatisfiesExpression:
37253729
return OperatorPrecedence.Relational;
37263730

37273731
case SyntaxKind.ThisKeyword:
@@ -3780,6 +3784,7 @@ namespace ts {
37803784
case SyntaxKind.InstanceOfKeyword:
37813785
case SyntaxKind.InKeyword:
37823786
case SyntaxKind.AsKeyword:
3787+
case SyntaxKind.SatisfiesKeyword:
37833788
return OperatorPrecedence.Relational;
37843789
case SyntaxKind.LessThanLessThanToken:
37853790
case SyntaxKind.GreaterThanGreaterThanToken:
@@ -5787,7 +5792,8 @@ namespace ts {
57875792
case SyntaxKind.PropertyAccessExpression:
57885793
case SyntaxKind.NonNullExpression:
57895794
case SyntaxKind.PartiallyEmittedExpression:
5790-
node = (node as CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression).expression;
5795+
case SyntaxKind.SatisfiesExpression:
5796+
node = (node as CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression | SatisfiesExpression).expression;
57915797
continue;
57925798
}
57935799

src/compiler/utilitiesPublic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,7 @@ namespace ts {
15921592
case SyntaxKind.OmittedExpression:
15931593
case SyntaxKind.CommaListExpression:
15941594
case SyntaxKind.PartiallyEmittedExpression:
1595+
case SyntaxKind.SatisfiesExpression:
15951596
return true;
15961597
default:
15971598
return isUnaryExpressionKind(kind);

src/compiler/visitorPublic.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,12 @@ namespace ts {
851851
nodeVisitor(node.expression, visitor, isExpression),
852852
nodeVisitor(node.type, visitor, isTypeNode));
853853

854+
case SyntaxKind.SatisfiesExpression:
855+
Debug.type<SatisfiesExpression>(node);
856+
return factory.updateSatisfiesExpression(node,
857+
nodeVisitor(node.expression, visitor, isExpression),
858+
nodeVisitor(node.type, visitor, isTypeNode));
859+
854860
case SyntaxKind.NonNullExpression:
855861
if (node.flags & NodeFlags.OptionalChain) {
856862
Debug.type<NonNullChain>(node);

src/harness/fourslashInterfaceImpl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,7 @@ namespace FourSlashInterface {
12991299
"readonly",
13001300
"number",
13011301
"object",
1302+
"satisfies",
13021303
"string",
13031304
"symbol",
13041305
"type",
@@ -1415,6 +1416,7 @@ namespace FourSlashInterface {
14151416
"as",
14161417
"async",
14171418
"await",
1419+
"satisfies",
14181420
].map(keywordEntry);
14191421

14201422
export const undefinedVarEntry: ExpectedCompletionEntryObject = {
@@ -1503,6 +1505,7 @@ namespace FourSlashInterface {
15031505
"readonly",
15041506
"number",
15051507
"object",
1508+
"satisfies",
15061509
"string",
15071510
"symbol",
15081511
"type",
@@ -1558,6 +1561,7 @@ namespace FourSlashInterface {
15581561
"as",
15591562
"async",
15601563
"await",
1564+
"satisfies",
15611565
].map(keywordEntry);
15621566

15631567
export const insideMethodInJsKeywords = getInJsKeywords(insideMethodKeywords);

0 commit comments

Comments
 (0)