Skip to content

Commit cc37e4f

Browse files
committed
feat(7481): add explicit type compatibility check with 'satisfies' expression
1 parent d417058 commit cc37e4f

38 files changed

+1231
-439
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26544,6 +26544,8 @@ namespace ts {
2654426544
}
2654526545
case SyntaxKind.NonNullExpression:
2654626546
return getContextualType(parent as NonNullExpression, contextFlags);
26547+
case SyntaxKind.SatisfiesExpression:
26548+
return getTypeFromTypeNode((parent as SatisfiesExpression).type);
2654726549
case SyntaxKind.JsxExpression:
2654826550
return getContextualTypeForJsxExpression(parent as JsxExpression);
2654926551
case SyntaxKind.JsxAttribute:
@@ -31348,6 +31350,20 @@ namespace ts {
3134831350
getNonNullableType(checkExpression(node.expression));
3134931351
}
3135031352

31353+
function checkSatisfiesExpression(node: SatisfiesExpression) {
31354+
checkSourceElement(node.type);
31355+
31356+
const targetType = getTypeFromTypeNode(node.type);
31357+
if (isErrorType(targetType)) {
31358+
return targetType;
31359+
}
31360+
31361+
const exprType = checkExpression(node.expression);
31362+
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
31363+
31364+
return exprType;
31365+
}
31366+
3135131367
function checkMetaProperty(node: MetaProperty): Type {
3135231368
checkGrammarMetaProperty(node);
3135331369

@@ -34100,6 +34116,8 @@ namespace ts {
3410034116
return checkAssertion(node as AssertionExpression);
3410134117
case SyntaxKind.NonNullExpression:
3410234118
return checkNonNullAssertion(node as NonNullExpression);
34119+
case SyntaxKind.SatisfiesExpression:
34120+
return checkSatisfiesExpression(node as SatisfiesExpression);
3410334121
case SyntaxKind.MetaProperty:
3410434122
return checkMetaProperty(node as MetaProperty);
3410534123
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+
"Type '{0}' does not satisfy the expected type '{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
@@ -6173,6 +6177,10 @@
61736177
"category": "Error",
61746178
"code": 8034
61756179
},
6180+
"Type satisfaction expressions can only be used in TypeScript files.": {
6181+
"category": "Error",
6182+
"code": 8035
6183+
},
61766184
"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
61776185
"category": "Error",
61786186
"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: 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 & 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:
@@ -4677,7 +4680,7 @@ namespace ts {
46774680
break;
46784681
}
46794682

4680-
if (token() === SyntaxKind.AsKeyword) {
4683+
if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) {
46814684
// Make sure we *do* perform ASI for constructs like this:
46824685
// var x = foo
46834686
// as (Bar)
@@ -4687,8 +4690,10 @@ namespace ts {
46874690
break;
46884691
}
46894692
else {
4693+
const keywordKind = token();
46904694
nextToken();
4691-
leftOperand = makeAsExpression(leftOperand, parseType());
4695+
leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) :
4696+
makeAsExpression(leftOperand, parseType());
46924697
}
46934698
}
46944699
else {
@@ -4707,6 +4712,10 @@ namespace ts {
47074712
return getBinaryOperatorPrecedence(token()) > 0;
47084713
}
47094714

4715+
function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression {
4716+
return finishNode(factory.createSatisfiesExpression(left, right), left.pos);
4717+
}
4718+
47104719
function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression {
47114720
return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos);
47124721
}

src/compiler/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,6 +2274,9 @@ namespace ts {
22742274
case SyntaxKind.AsExpression:
22752275
diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files));
22762276
return "skip";
2277+
case SyntaxKind.SatisfiesExpression:
2278+
diagnostics.push(createDiagnosticForNode((node as SatisfiesExpression).type, Diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files));
2279+
return "skip";
22772280
case SyntaxKind.TypeAssertionExpression:
22782281
Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX.
22792282
}

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

@@ -2257,6 +2260,11 @@ namespace ts {
22572260
return factory.createPartiallyEmittedExpression(expression, node);
22582261
}
22592262

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

0 commit comments

Comments
 (0)