Skip to content

Commit

Permalink
feat(7481): add explicit type compatibility check with 'satisfies' ex…
Browse files Browse the repository at this point in the history
…pression
  • Loading branch information
a-tarasyuk committed Dec 12, 2021
1 parent d417058 commit cc37e4f
Show file tree
Hide file tree
Showing 38 changed files with 1,231 additions and 439 deletions.
18 changes: 18 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26544,6 +26544,8 @@ namespace ts {
}
case SyntaxKind.NonNullExpression:
return getContextualType(parent as NonNullExpression, contextFlags);
case SyntaxKind.SatisfiesExpression:
return getTypeFromTypeNode((parent as SatisfiesExpression).type);
case SyntaxKind.JsxExpression:
return getContextualTypeForJsxExpression(parent as JsxExpression);
case SyntaxKind.JsxAttribute:
Expand Down Expand Up @@ -31348,6 +31350,20 @@ namespace ts {
getNonNullableType(checkExpression(node.expression));
}

function checkSatisfiesExpression(node: SatisfiesExpression) {
checkSourceElement(node.type);

const targetType = getTypeFromTypeNode(node.type);
if (isErrorType(targetType)) {
return targetType;
}

const exprType = checkExpression(node.expression);
checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node.type, node.expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);

return exprType;
}

function checkMetaProperty(node: MetaProperty): Type {
checkGrammarMetaProperty(node);

Expand Down Expand Up @@ -34100,6 +34116,8 @@ namespace ts {
return checkAssertion(node as AssertionExpression);
case SyntaxKind.NonNullExpression:
return checkNonNullAssertion(node as NonNullExpression);
case SyntaxKind.SatisfiesExpression:
return checkSatisfiesExpression(node as SatisfiesExpression);
case SyntaxKind.MetaProperty:
return checkMetaProperty(node as MetaProperty);
case SyntaxKind.DeleteExpression:
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,10 @@
"category": "Error",
"code": 1359
},
"Type '{0}' does not satisfy the expected type '{1}'.": {
"category": "Error",
"code": 1360
},
"'{0}' cannot be used as a value because it was imported using 'import type'.": {
"category": "Error",
"code": 1361
Expand Down Expand Up @@ -6173,6 +6177,10 @@
"category": "Error",
"code": 8034
},
"Type satisfaction expressions can only be used in TypeScript files.": {
"category": "Error",
"code": 8035
},
"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
"category": "Error",
"code": 9005
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,8 @@ namespace ts {
return emitAsExpression(node as AsExpression);
case SyntaxKind.NonNullExpression:
return emitNonNullExpression(node as NonNullExpression);
case SyntaxKind.SatisfiesExpression:
return emitSatisfiesExpression(node as SatisfiesExpression);
case SyntaxKind.MetaProperty:
return emitMetaProperty(node as MetaProperty);
case SyntaxKind.SyntheticExpression:
Expand Down Expand Up @@ -2799,6 +2801,16 @@ namespace ts {
writeOperator("!");
}

function emitSatisfiesExpression(node: SatisfiesExpression) {
emitExpression(node.expression, /*parenthesizerRules*/ undefined);
if (node.type) {
writeSpace();
writeKeyword("satisfies");
writeSpace();
emit(node.type);
}
}

function emitMetaProperty(node: MetaProperty) {
writeToken(node.keywordToken, node.pos, writePunctuation);
writePunctuation(".");
Expand Down
24 changes: 24 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ namespace ts {
updateAsExpression,
createNonNullExpression,
updateNonNullExpression,
createSatisfiesExpression,
updateSatisfiesExpression,
createNonNullChain,
updateNonNullChain,
createMetaProperty,
Expand Down Expand Up @@ -3099,6 +3101,26 @@ namespace ts {
: node;
}

// @api
function createSatisfiesExpression(expression: Expression, type: TypeNode) {
const node = createBaseExpression<SatisfiesExpression>(SyntaxKind.SatisfiesExpression);
node.expression = expression;
node.type = type;
node.transformFlags |=
propagateChildFlags(node.expression) |
propagateChildFlags(node.type) |
TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode) {
return node.expression !== expression
|| node.type !== type
? update(createSatisfiesExpression(expression, type), node)
: node;
}

// @api
function createNonNullChain(expression: Expression) {
const node = createBaseExpression<NonNullChain>(SyntaxKind.NonNullExpression);
Expand Down Expand Up @@ -5590,6 +5612,7 @@ namespace ts {
case SyntaxKind.ParenthesizedExpression: return updateParenthesizedExpression(outerExpression, expression);
case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression);
case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type);
case SyntaxKind.SatisfiesExpression: return updateSatisfiesExpression(outerExpression, expression, outerExpression.type);
case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression);
case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression);
}
Expand Down Expand Up @@ -6320,6 +6343,7 @@ namespace ts {
case SyntaxKind.ArrayBindingPattern:
return TransformFlags.BindingPatternExcludes;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.SatisfiesExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.PartiallyEmittedExpression:
case SyntaxKind.ParenthesizedExpression:
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@ namespace ts {
return node.kind === SyntaxKind.AsExpression;
}

export function isSatisfiesExpression(node: Node): node is SatisfiesExpression {
return node.kind === SyntaxKind.SatisfiesExpression;
}

export function isNonNullExpression(node: Node): node is NonNullExpression {
return node.kind === SyntaxKind.NonNullExpression;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ namespace ts {
return (kinds & OuterExpressionKinds.Parentheses) !== 0;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.SatisfiesExpression:
return (kinds & OuterExpressionKinds.TypeAssertions) !== 0;
case SyntaxKind.NonNullExpression:
return (kinds & OuterExpressionKinds.NonNullAssertions) !== 0;
Expand Down
13 changes: 11 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ namespace ts {
visitNode(cbNode, (node as AsExpression).type);
case SyntaxKind.NonNullExpression:
return visitNode(cbNode, (node as NonNullExpression).expression);
case SyntaxKind.SatisfiesExpression:
return visitNode(cbNode, (node as SatisfiesExpression).expression) ||
visitNode(cbNode, (node as SatisfiesExpression).type);
case SyntaxKind.MetaProperty:
return visitNode(cbNode, (node as MetaProperty).name);
case SyntaxKind.ConditionalExpression:
Expand Down Expand Up @@ -4677,7 +4680,7 @@ namespace ts {
break;
}

if (token() === SyntaxKind.AsKeyword) {
if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) {
// Make sure we *do* perform ASI for constructs like this:
// var x = foo
// as (Bar)
Expand All @@ -4687,8 +4690,10 @@ namespace ts {
break;
}
else {
const keywordKind = token();
nextToken();
leftOperand = makeAsExpression(leftOperand, parseType());
leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) :
makeAsExpression(leftOperand, parseType());
}
}
else {
Expand All @@ -4707,6 +4712,10 @@ namespace ts {
return getBinaryOperatorPrecedence(token()) > 0;
}

function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression {
return finishNode(factory.createSatisfiesExpression(left, right), left.pos);
}

function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression {
return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos);
}
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,9 @@ namespace ts {
case SyntaxKind.AsExpression:
diagnostics.push(createDiagnosticForNode((node as AsExpression).type, Diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files));
return "skip";
case SyntaxKind.SatisfiesExpression:
diagnostics.push(createDiagnosticForNode((node as SatisfiesExpression).type, Diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files));
return "skip";
case SyntaxKind.TypeAssertionExpression:
Debug.fail(); // Won't parse these in a JS file anyway, as they are interpreted as JSX.
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ namespace ts {
require: SyntaxKind.RequireKeyword,
global: SyntaxKind.GlobalKeyword,
return: SyntaxKind.ReturnKeyword,
satisfies: SyntaxKind.SatisfiesKeyword,
set: SyntaxKind.SetKeyword,
static: SyntaxKind.StaticKeyword,
string: SyntaxKind.StringKeyword,
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ namespace ts {
// TypeScript type assertions are removed, but their subtrees are preserved.
return visitAssertionExpression(node as AssertionExpression);

case SyntaxKind.SatisfiesExpression:
return visitSatisfiesExpression(node as SatisfiesExpression);

case SyntaxKind.CallExpression:
return visitCallExpression(node as CallExpression);

Expand Down Expand Up @@ -2257,6 +2260,11 @@ namespace ts {
return factory.createPartiallyEmittedExpression(expression, node);
}

function visitSatisfiesExpression(node: SatisfiesExpression): Expression {
const expression = visitNode(node.expression, visitor, isExpression);
return factory.createPartiallyEmittedExpression(expression, node);
}

function visitCallExpression(node: CallExpression) {
return factory.updateCallExpression(
node,
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ namespace ts {
RequireKeyword,
NumberKeyword,
ObjectKeyword,
SatisfiesKeyword,
SetKeyword,
StringKeyword,
SymbolKeyword,
Expand Down Expand Up @@ -273,6 +274,7 @@ namespace ts {
NonNullExpression,
MetaProperty,
SyntheticExpression,
SatisfiesExpression,

// Misc
TemplateSpan,
Expand Down Expand Up @@ -602,6 +604,7 @@ namespace ts {
| SyntaxKind.OverrideKeyword
| SyntaxKind.RequireKeyword
| SyntaxKind.ReturnKeyword
| SyntaxKind.SatisfiesKeyword
| SyntaxKind.SetKeyword
| SyntaxKind.StaticKeyword
| SyntaxKind.StringKeyword
Expand Down Expand Up @@ -2485,6 +2488,12 @@ namespace ts {
readonly expression: UnaryExpression;
}

export interface SatisfiesExpression extends Expression {
readonly kind: SyntaxKind.SatisfiesExpression;
readonly expression: Expression;
readonly type: TypeNode;
}

export type AssertionExpression =
| TypeAssertion
| AsExpression
Expand Down Expand Up @@ -7037,6 +7046,7 @@ namespace ts {
export type OuterExpression =
| ParenthesizedExpression
| TypeAssertion
| SatisfiesExpression
| AsExpression
| NonNullExpression
| PartiallyEmittedExpression;
Expand Down Expand Up @@ -7366,6 +7376,8 @@ namespace ts {
updateNonNullChain(node: NonNullChain, expression: Expression): NonNullChain;
createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty;
updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty;
createSatisfiesExpression(expression: Expression, type: TypeNode): SatisfiesExpression;
updateSatisfiesExpression(node: SatisfiesExpression, expression: Expression, type: TypeNode): SatisfiesExpression;

//
// Misc
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,7 @@ namespace ts {
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.SatisfiesExpression:
case SyntaxKind.NonNullExpression:
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.FunctionExpression:
Expand Down Expand Up @@ -2019,6 +2020,8 @@ namespace ts {
return (parent as ExpressionWithTypeArguments).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent);
case SyntaxKind.ShorthandPropertyAssignment:
return (parent as ShorthandPropertyAssignment).objectAssignmentInitializer === node;
case SyntaxKind.SatisfiesExpression:
return node === (parent as SatisfiesExpression).expression;
default:
return isExpressionNode(parent);
}
Expand Down Expand Up @@ -3707,6 +3710,7 @@ namespace ts {
return OperatorPrecedence.Member;

case SyntaxKind.AsExpression:
case SyntaxKind.SatisfiesExpression:
return OperatorPrecedence.Relational;

case SyntaxKind.ThisKeyword:
Expand Down Expand Up @@ -3765,6 +3769,7 @@ namespace ts {
case SyntaxKind.InstanceOfKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.AsKeyword:
case SyntaxKind.SatisfiesKeyword:
return OperatorPrecedence.Relational;
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
Expand Down Expand Up @@ -5772,7 +5777,8 @@ namespace ts {
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.NonNullExpression:
case SyntaxKind.PartiallyEmittedExpression:
node = (node as CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression).expression;
case SyntaxKind.SatisfiesExpression:
node = (node as CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression | SatisfiesExpression).expression;
continue;
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,7 @@ namespace ts {
case SyntaxKind.OmittedExpression:
case SyntaxKind.CommaListExpression:
case SyntaxKind.PartiallyEmittedExpression:
case SyntaxKind.SatisfiesExpression:
return true;
default:
return isUnaryExpressionKind(kind);
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,12 @@ namespace ts {
nodeVisitor(node.expression, visitor, isExpression),
nodeVisitor(node.type, visitor, isTypeNode));

case SyntaxKind.SatisfiesExpression:
Debug.type<SatisfiesExpression>(node);
return factory.updateSatisfiesExpression(node,
nodeVisitor(node.expression, visitor, isExpression),
nodeVisitor(node.type, visitor, isTypeNode));

case SyntaxKind.NonNullExpression:
if (node.flags & NodeFlags.OptionalChain) {
Debug.type<NonNullChain>(node);
Expand Down
4 changes: 4 additions & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,7 @@ namespace FourSlashInterface {
"package",
"readonly",
"return",
"satisfies",
"string",
"super",
"switch",
Expand Down Expand Up @@ -1461,6 +1462,7 @@ namespace FourSlashInterface {
"null",
"package",
"return",
"satisfies",
"super",
"switch",
"this",
Expand Down Expand Up @@ -1558,6 +1560,7 @@ namespace FourSlashInterface {
"package",
"readonly",
"return",
"satisfies",
"string",
"super",
"switch",
Expand Down Expand Up @@ -1612,6 +1615,7 @@ namespace FourSlashInterface {
"null",
"package",
"return",
"satisfies",
"super",
"switch",
"this",
Expand Down
Loading

0 comments on commit cc37e4f

Please sign in to comment.