Skip to content

[Experiment] feat(51556): support satisfies operator on functions/classes #57859

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 19 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ import {
ReverseMappedSymbol,
ReverseMappedType,
sameMap,
SatisfiesClause,
SatisfiesExpression,
scanTokenAtPosition,
ScriptKind,
Expand Down Expand Up @@ -7797,7 +7798,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
kind === SyntaxKind.JSDocFunctionType ? factory.createJSDocFunctionType(parameters, returnTypeNode) :
kind === SyntaxKind.FunctionType ? factory.createFunctionTypeNode(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) :
kind === SyntaxKind.ConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) :
kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) :
kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined, /*satisfiesClause*/ undefined) :
kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) :
kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) :
Debug.assertNever(kind);
Expand Down Expand Up @@ -9684,6 +9685,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
typeParamDecls,
heritageClauses,
[...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties],
/*satisfiesClause*/ undefined,
),
symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0],
),
Expand Down Expand Up @@ -36480,6 +36482,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function checkSatisfiesClause(sourceType: Type, node: SatisfiesClause) {
checkSourceElement(node.type);
const targetType = getTypeFromTypeNode(node.type);
if (isErrorType(targetType)) {
return targetType;
}
checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, node, /*expr*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1);
}

function checkSatisfiesExpression(node: SatisfiesExpression) {
checkSourceElement(node.type);
return checkSatisfiesExpressionWorker(node.expression, node.type);
Expand Down Expand Up @@ -42403,6 +42414,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

if (isFunctionDeclaration(node) && node.satisfiesClause) {
checkSatisfiesClause(getTypeOfSymbol(getSymbolOfDeclaration(node)), node.satisfiesClause);
}

function checkFunctionOrMethodDeclarationDiagnostics() {
if (!getEffectiveReturnTypeNode(node)) {
// Report an implicit any error if there is no body, no explicit return type, and node is not a private method
Expand Down Expand Up @@ -45004,7 +45019,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
checkClassLikeDeclaration(node);
forEach(node.members, checkSourceElement);

if (node.satisfiesClause) {
checkSatisfiesClause(getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node)), node.satisfiesClause);
}
registerForUnusedIdentifiersCheck(node);
}

Expand Down
16 changes: 16 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ import {
resolvePath,
RestTypeNode,
ReturnStatement,
SatisfiesClause,
SatisfiesExpression,
ScriptTarget,
setOriginalNode,
Expand Down Expand Up @@ -1742,6 +1743,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitHeritageClause(node as HeritageClause);
case SyntaxKind.CatchClause:
return emitCatchClause(node as CatchClause);
case SyntaxKind.SatisfiesClause:
return emitSatisfiesClause(node as SatisfiesClause);

// Property assignments
case SyntaxKind.PropertyAssignment:
Expand Down Expand Up @@ -3292,6 +3295,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri

function emitFunctionDeclaration(node: FunctionDeclaration) {
emitFunctionDeclarationOrExpression(node);
if (node.satisfiesClause) {
emit(node.satisfiesClause);
}
}

function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) {
Expand Down Expand Up @@ -3420,6 +3426,9 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri

function emitClassDeclaration(node: ClassDeclaration) {
emitClassDeclarationOrExpression(node);
if (node.satisfiesClause) {
emit(node.satisfiesClause);
}
}

function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) {
Expand Down Expand Up @@ -3916,6 +3925,13 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
emit(node.block);
}

function emitSatisfiesClause(node: SatisfiesClause) {
writeSpace();
emitTokenWithComment(SyntaxKind.SatisfiesKeyword, node.pos, writeKeyword, node);
writeSpace();
emit(node.type);
}

//
// Property assignments
//
Expand Down
36 changes: 31 additions & 5 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ import {
ReturnStatement,
returnTrue,
sameFlatMap,
SatisfiesClause,
SatisfiesExpression,
Scanner,
ScriptTarget,
Expand Down Expand Up @@ -990,6 +991,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateHeritageClause,
createCatchClause,
updateCatchClause,
createSatisfiesClause,
updateSatisfiesClause,
createPropertyAssignment,
updatePropertyAssignment,
createShorthandPropertyAssignment,
Expand Down Expand Up @@ -4294,6 +4297,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
parameters: readonly ParameterDeclaration[],
type: TypeNode | undefined,
body: Block | undefined,
satisfiesClause: SatisfiesClause | undefined,
) {
const node = createBaseDeclaration<FunctionDeclaration>(SyntaxKind.FunctionDeclaration);
node.modifiers = asNodeArray(modifiers);
Expand All @@ -4303,6 +4307,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
node.parameters = createNodeArray(parameters);
node.type = type;
node.body = body;
node.satisfiesClause = satisfiesClause;

if (!node.body || modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) {
node.transformFlags = TransformFlags.ContainsTypeScript;
Expand Down Expand Up @@ -4346,6 +4351,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
parameters: readonly ParameterDeclaration[],
type: TypeNode | undefined,
body: Block | undefined,
satisfiesClause: SatisfiesClause | undefined,
) {
return node.modifiers !== modifiers
|| node.asteriskToken !== asteriskToken
Expand All @@ -4354,7 +4360,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|| node.parameters !== parameters
|| node.type !== type
|| node.body !== body
? finishUpdateFunctionDeclaration(createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body), node)
|| node.satisfiesClause !== satisfiesClause
? finishUpdateFunctionDeclaration(createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body, satisfiesClause), node)
: node;
}

Expand All @@ -4375,13 +4382,15 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
typeParameters: readonly TypeParameterDeclaration[] | undefined,
heritageClauses: readonly HeritageClause[] | undefined,
members: readonly ClassElement[],
satisfiesClause: SatisfiesClause | undefined,
) {
const node = createBaseDeclaration<ClassDeclaration>(SyntaxKind.ClassDeclaration);
node.modifiers = asNodeArray(modifiers);
node.name = asName(name);
node.typeParameters = asNodeArray(typeParameters);
node.heritageClauses = asNodeArray(heritageClauses);
node.members = createNodeArray(members);
node.satisfiesClause = satisfiesClause;

if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) {
node.transformFlags = TransformFlags.ContainsTypeScript;
Expand Down Expand Up @@ -4411,13 +4420,15 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
typeParameters: readonly TypeParameterDeclaration[] | undefined,
heritageClauses: readonly HeritageClause[] | undefined,
members: readonly ClassElement[],
satisfiesClause: SatisfiesClause | undefined,
) {
return node.modifiers !== modifiers
|| node.name !== name
|| node.typeParameters !== typeParameters
|| node.heritageClauses !== heritageClauses
|| node.members !== members
? update(createClassDeclaration(modifiers, name, typeParameters, heritageClauses, members), node)
|| node.satisfiesClause !== satisfiesClause
? update(createClassDeclaration(modifiers, name, typeParameters, heritageClauses, members, satisfiesClause), node)
: node;
}

Expand Down Expand Up @@ -5897,6 +5908,21 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}

// @api
function createSatisfiesClause(type: TypeNode) {
const node = createBaseNode<SatisfiesClause>(SyntaxKind.SatisfiesClause);
node.type = type;
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateSatisfiesClause(node: SatisfiesClause, type: TypeNode) {
return node.type !== type
? update(createSatisfiesClause(type), node)
: node;
}

//
// Property assignments
//
Expand Down Expand Up @@ -7064,8 +7090,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
isArrowFunction(node) ? updateArrowFunction(node, modifierArray, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) :
isClassExpression(node) ? updateClassExpression(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
isVariableStatement(node) ? updateVariableStatement(node, modifierArray, node.declarationList) :
isFunctionDeclaration(node) ? updateFunctionDeclaration(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) :
isClassDeclaration(node) ? updateClassDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
isFunctionDeclaration(node) ? updateFunctionDeclaration(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body, node.satisfiesClause) :
isClassDeclaration(node) ? updateClassDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members, node.satisfiesClause) :
isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, modifierArray, node.name, node.typeParameters, node.type) :
isEnumDeclaration(node) ? updateEnumDeclaration(node, modifierArray, node.name, node.members) :
Expand All @@ -7085,7 +7111,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, modifierArray, node.name, node.parameters, node.type, node.body) :
isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, modifierArray, node.name, node.parameters, node.body) :
isClassExpression(node) ? updateClassExpression(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
isClassDeclaration(node) ? updateClassDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) :
isClassDeclaration(node) ? updateClassDeclaration(node, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members, node.satisfiesClause) :
Debug.assertNever(node);
}

Expand Down
5 changes: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ import {
RegularExpressionLiteral,
RestTypeNode,
ReturnStatement,
SatisfiesClause,
SatisfiesExpression,
SemicolonClassElement,
SetAccessorDeclaration,
Expand Down Expand Up @@ -985,6 +986,10 @@ export function isCatchClause(node: Node): node is CatchClause {
return node.kind === SyntaxKind.CatchClause;
}

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

// Property assignments

export function isPropertyAssignment(node: Node): node is PropertyAssignment {
Expand Down
Loading