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>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-16 17:54:24 +00:00
parent c0ff10a906
commit cf4425cd6b
4 changed files with 12 additions and 45 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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() {