Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(7481): Operator to ensure an expression is contextually typed by, and satisfies, some type #46827

Merged
merged 19 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2fb109d
feat(7481): add explicit type compatibility check with 'satisfies' ex…
a-tarasyuk Feb 17, 2022
b7e1c94
Merge branch 'main' into feat/7481
RyanCavanaugh Mar 17, 2022
d09c6c6
Add failing test for lack of intersectioned contextual type
RyanCavanaugh Mar 17, 2022
ee8e50e
Implement the behavior
RyanCavanaugh Mar 17, 2022
d810a02
Add test corresponding to the 'if'
RyanCavanaugh Mar 17, 2022
bdd6b3e
Add test based on defined scenarios
RyanCavanaugh Mar 17, 2022
dfc9d98
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Mar 23, 2022
95983bd
remove isExpression in favor of using type casting
a-tarasyuk Mar 23, 2022
a9050e5
move tests from compiler to conformance folder
a-tarasyuk Mar 23, 2022
4175a31
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk May 17, 2022
5a864db
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk May 27, 2022
203cffc
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jun 14, 2022
834c1f9
update baseline
a-tarasyuk Jun 14, 2022
85cc292
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jun 17, 2022
202ed33
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Jul 23, 2022
a243eb6
add missing contextFlags argument
a-tarasyuk Jul 23, 2022
9842285
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Aug 25, 2022
66d7ca0
use asserted type
a-tarasyuk Aug 25, 2022
a73f1c7
accept baseline
a-tarasyuk Aug 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27505,6 +27505,8 @@ namespace ts {
}
case SyntaxKind.NonNullExpression:
return getContextualType(parent as NonNullExpression, contextFlags);
case SyntaxKind.SatisfiesExpression:
return getTypeFromTypeNode((parent as SatisfiesExpression).type);
case SyntaxKind.ExportAssignment:
return tryGetTypeFromEffectiveTypeNode(parent as ExportAssignment);
case SyntaxKind.JsxExpression:
Expand Down Expand Up @@ -32441,6 +32443,20 @@ namespace ts {
}
}

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 @@ -35234,6 +35250,8 @@ namespace ts {
return checkNonNullAssertion(node as NonNullExpression);
case SyntaxKind.ExpressionWithTypeArguments:
return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments);
case SyntaxKind.SatisfiesExpression:
return checkSatisfiesExpression(node as SatisfiesExpression);
case SyntaxKind.MetaProperty:
return checkMetaProperty(node as MetaProperty);
case SyntaxKind.DeleteExpression:
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@
"category": "Error",
"code": 1359
},
"Class constructor may not be a generator.": {
"Type '{0}' does not satisfy the expected type '{1}'.": {
"category": "Error",
"code": 1360
},
Expand Down Expand Up @@ -1132,6 +1132,10 @@
"category": "Message",
"code": 1367
},
"Class constructor may not be a generator.": {
"category": "Error",
"code": 1368
},
"Did you mean '{0}'?": {
"category": "Message",
"code": 1369
Expand Down Expand Up @@ -6376,6 +6380,10 @@
"category": "Error",
"code": 8036
},
"Type satisfaction expressions can only be used in TypeScript files.": {
a-tarasyuk marked this conversation as resolved.
Show resolved Hide resolved
"category": "Error",
"code": 8037
},

"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
"category": "Error",
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,8 @@ namespace ts {
return emitNonNullExpression(node as NonNullExpression);
case SyntaxKind.ExpressionWithTypeArguments:
return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments);
case SyntaxKind.SatisfiesExpression:
return emitSatisfiesExpression(node as SatisfiesExpression);
case SyntaxKind.MetaProperty:
return emitMetaProperty(node as MetaProperty);
case SyntaxKind.SyntheticExpression:
Expand Down Expand Up @@ -2846,6 +2848,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 @@ -222,6 +222,8 @@ namespace ts {
updateAsExpression,
createNonNullExpression,
updateNonNullExpression,
createSatisfiesExpression,
updateSatisfiesExpression,
createNonNullChain,
updateNonNullChain,
createMetaProperty,
Expand Down Expand Up @@ -3142,6 +3144,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 @@ -5730,6 +5752,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 @@ -6465,6 +6488,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 @@ -438,6 +438,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
14 changes: 11 additions & 3 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ namespace ts {
[SyntaxKind.NonNullExpression]: function forEachChildInNonNullExpression<T>(node: NonNullExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.expression);
},
[SyntaxKind.SatisfiesExpression]: function forEachChildInSatisfiesExpression<T>(node: SatisfiesExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.expression) || visitNode(cbNode, node.type);
},
[SyntaxKind.MetaProperty]: function forEachChildInMetaProperty<T>(node: MetaProperty, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.name);
},
Expand Down Expand Up @@ -841,7 +844,6 @@ namespace ts {
if (node === undefined || node.kind <= SyntaxKind.LastToken) {
return;
}

const fn = (forEachChildTable as Record<SyntaxKind, ForEachChildFunction<any>>)[node.kind];
return fn === undefined ? undefined : fn(node, cbNode, cbNodes);
}
Expand Down Expand Up @@ -5109,7 +5111,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 @@ -5119,8 +5121,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 @@ -5139,6 +5143,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 @@ -2372,6 +2372,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 @@ -136,6 +136,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 @@ -529,6 +529,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 @@ -1421,6 +1424,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
13 changes: 13 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ namespace ts {
RequireKeyword,
NumberKeyword,
ObjectKeyword,
SatisfiesKeyword,
SetKeyword,
StringKeyword,
SymbolKeyword,
Expand Down Expand Up @@ -274,6 +275,7 @@ namespace ts {
NonNullExpression,
MetaProperty,
SyntheticExpression,
SatisfiesExpression,

// Misc
TemplateSpan,
Expand Down Expand Up @@ -607,6 +609,7 @@ namespace ts {
| SyntaxKind.OverrideKeyword
| SyntaxKind.RequireKeyword
| SyntaxKind.ReturnKeyword
| SyntaxKind.SatisfiesKeyword
| SyntaxKind.SetKeyword
| SyntaxKind.StaticKeyword
| SyntaxKind.StringKeyword
Expand Down Expand Up @@ -1007,6 +1010,7 @@ namespace ts {
| ExpressionWithTypeArguments
| AsExpression
| NonNullExpression
| SatisfiesExpression
| MetaProperty
| TemplateSpan
| Block
Expand Down Expand Up @@ -2815,6 +2819,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 @@ -7518,6 +7528,7 @@ namespace ts {
export type OuterExpression =
| ParenthesizedExpression
| TypeAssertion
| SatisfiesExpression
| AsExpression
| NonNullExpression
| PartiallyEmittedExpression;
Expand Down Expand Up @@ -7854,6 +7865,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 @@ -1995,6 +1995,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 @@ -2095,6 +2096,8 @@ namespace ts {
return (parent as ExpressionWithTypeArguments).expression === node && !isPartOfTypeNode(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 @@ -3801,6 +3804,7 @@ namespace ts {
return OperatorPrecedence.Member;

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

case SyntaxKind.ThisKeyword:
Expand Down Expand Up @@ -3859,6 +3863,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 @@ -5930,7 +5935,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 @@ -1642,6 +1642,7 @@ namespace ts {
case SyntaxKind.OmittedExpression:
case SyntaxKind.CommaListExpression:
case SyntaxKind.PartiallyEmittedExpression:
case SyntaxKind.SatisfiesExpression:
return true;
default:
return isUnaryExpressionKind(kind);
Expand Down
8 changes: 7 additions & 1 deletion src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,13 +888,19 @@ namespace ts {
nodeVisitor(node.type, visitor, isTypeNode));
},

[SyntaxKind.SatisfiesExpression]: function visitEachChildOfSatisfiesExpression(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) {
return context.factory.updateSatisfiesExpression(node,
nodeVisitor(node.expression, visitor, isExpression),
nodeVisitor(node.type, visitor, isTypeNode));
},

[SyntaxKind.NonNullExpression]: function visitEachChildOfNonNullExpression(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) {
return isOptionalChain(node) ?
context.factory.updateNonNullChain(node,
nodeVisitor(node.expression, visitor, isExpression)) :
context.factory.updateNonNullExpression(node,
nodeVisitor(node.expression, visitor, isExpression));
},
},

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