From cf4425cd6ba7a0344e792b841cc474ce0c89c2b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:54:24 +0000 Subject: [PATCH] WIP: Add guards to prevent infinite recursion in type checking - Added parser fix for incomplete keyof operator (missing operand) - Working on checker fix for stack overflow with union types + destructuring + undefined shorthand properties - Current approach: prevent recursion in isConstContext when computing contextual types Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- debug-ast.mjs | 20 -------------------- debug-ast2.mjs | 23 ----------------------- src/compiler/checker.ts | 3 ++- src/compiler/parser.ts | 11 ++++++++++- 4 files changed, 12 insertions(+), 45 deletions(-) delete mode 100644 debug-ast.mjs delete mode 100644 debug-ast2.mjs diff --git a/debug-ast.mjs b/debug-ast.mjs deleted file mode 100644 index 49326cc435f..00000000000 --- a/debug-ast.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import * as ts from "./built/local/typescript.js"; - -const sourceFile = ts.createSourceFile( - "test.ts", - `const { c, f }: keyof = { c: 0, f };`, - ts.ScriptTarget.Latest, - true -); - -function printNode(node, indent = 0) { - const prefix = " ".repeat(indent); - console.log(`${prefix}${ts.SyntaxKind[node.kind]}`); - if (ts.isTypeOperatorNode(node)) { - console.log(`${prefix} operator: ${ts.SyntaxKind[node.operator]}`); - console.log(`${prefix} type: ${ts.SyntaxKind[node.type.kind]}`); - } - ts.forEachChild(node, child => printNode(child, indent + 1)); -} - -printNode(sourceFile); diff --git a/debug-ast2.mjs b/debug-ast2.mjs deleted file mode 100644 index ef71fcdd90d..00000000000 --- a/debug-ast2.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import * as ts from "./built/local/typescript.js"; - -const sourceFile = ts.createSourceFile( - "test.ts", - `const { c, f }: keyof = { c: 0, f };`, - ts.ScriptTarget.Latest, - true -); - -function printNode(node, indent = 0) { - const prefix = " ".repeat(indent); - let info = `${prefix}${ts.SyntaxKind[node.kind]}`; - if (ts.isTypeOperatorNode(node)) { - info += ` (operator: ${ts.SyntaxKind[node.operator]})`; - } - if (ts.isIdentifier(node)) { - info += ` (text: "${node.text}")`; - } - console.log(info); - ts.forEachChild(node, child => printNode(child, indent + 1)); -} - -printNode(sourceFile); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 390c843b0c9..15a38823e0b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -41397,7 +41397,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const parent = node.parent; return isAssertionExpression(parent) && isConstTypeReference(parent.type) || isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || - isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || + // Avoid calling getContextualType if we're already computing contextual types to prevent infinite recursion + isValidConstAssertionArgument(node) && findContextualNode(node, /*includeCaches*/ true) < 0 && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 68133ac5f1e..1ea9c22fd2e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4752,7 +4752,16 @@ namespace Parser { function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { const pos = getNodePos(); parseExpected(operator); - return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); + // Check if the next token is a valid type start. If not, report an error and use 'any' as a placeholder. + let type: TypeNode; + if (isStartOfType()) { + type = parseTypeOperatorOrHigher(); + } + else { + parseErrorAtCurrentToken(Diagnostics.Type_expected); + type = finishNode(factory.createToken(SyntaxKind.AnyKeyword), getNodePos()); + } + return finishNode(factory.createTypeOperatorNode(operator, type), pos); } function tryParseConstraintOfInferType() {