From eb820167b865100501ae6c79c85baab823e83d94 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 12 Nov 2026 21:03:11 +0000 Subject: [PATCH 1/3] for...in walks an object literal's fields; a generic's instance makes other instances `function main() { print(JSON.stringify({x: 1})); }` exited with 0xC0000409 (exit code 3 without --opt) and printed nothing under every memory model. The default library threw its TypeError for an object literal, by design, and nothing caught it. Two compiler gaps stood in the way of writing the object instead: - for...in over an object literal or a tuple used `.length` and failed to compile. It is now made for each field of the type: `for (.i < N) { if (.i === 0) { const key = "a"; body } else if ... }`, so `obj[key]` is that field with its own type and break/continue keep their meaning. An unnamed field's key is its position, and `t["1"]` and `"1" in t` find it, as in JavaScript. - GenericFunctionInfo::processing was one flag per generic function, so while `f` was being made, a call of `f` from its body was taken for recursion into an unfinished function and failed with "not all generic types can be identified". `JSON.stringify([[1], [2]])` failed this way in any program that had not stringified a number first. It is now the set of instances being made; another instance is made then, up to 64 deep (`f` calling `f` would never end). Tests: 00for_in_fields.ts, 00generic_instance_from_instance.ts (gc, own, rc, none; compile and JIT), and the object half of 00for_in.ts, commented out until now. Co-Authored-By: Claude Opus 5.5 --- tslang/include/TypeScript/Defines.h | 2 + .../TypeScript/MLIRLogic/MLIRCodeLogic.h | 11 ++ .../TypeScript/MLIRLogic/MLIRGenStore.h | 7 +- tslang/lib/TypeScript/MLIRGenGenerics.cpp | 34 +++-- tslang/lib/TypeScript/MLIRGenImpl.h | 12 ++ tslang/lib/TypeScript/MLIRGenStatements.cpp | 130 ++++++++++++++++++ tslang/test/tester/CMakeLists.txt | 14 ++ tslang/test/tester/tests/00for_in.ts | 10 +- tslang/test/tester/tests/00for_in_fields.ts | 69 ++++++++++ .../tests/00generic_instance_from_instance.ts | 42 ++++++ 10 files changed, 311 insertions(+), 20 deletions(-) create mode 100644 tslang/test/tester/tests/00for_in_fields.ts create mode 100644 tslang/test/tester/tests/00generic_instance_from_instance.ts diff --git a/tslang/include/TypeScript/Defines.h b/tslang/include/TypeScript/Defines.h index e9ecd17b3..cfa4f70c7 100644 --- a/tslang/include/TypeScript/Defines.h +++ b/tslang/include/TypeScript/Defines.h @@ -6,6 +6,8 @@ #define IDENTIFIER_ATTR_NAME "identifier" #define BUILTIN_FUNC_ATTR_NAME "__builtin" #define GENERIC_ATTR_NAME "__generic" +// instances of one generic function being made at once, each called from the one before (MLIRGenGenerics.cpp) +#define MAX_NESTED_GENERIC_INSTANCES 64 #define ATOMIC_ATTR_NAME "__atomic" #define ORDERING_ATTR_NAME "__ordering" #define SYNCSCOPE_ATTR_NAME "__syncscope" diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h b/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h index 0b0079684..a31de5b15 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h @@ -252,6 +252,17 @@ class MLIRCodeLogic fieldIndex = intAttr.getValue().getSExtValue();; notFound = fieldIndex < 0 || fieldIndex >= tupleType.size(); } + // `t["1"]`, as `for (const k in t)` gives it: the position of a field that has no name + else if (auto strAttr = dyn_cast(fieldId)) + { + unsigned position; + if (!strAttr.getValue().getAsInteger(10, position) && position < tupleType.size() + && !isa_and_nonnull(tupleType.getFieldInfo(position).id)) + { + fieldIndex = position; + notFound = false; + } + } } if (notFound) diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index bcd5732c9..378868c08 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -6,6 +6,7 @@ #include "TypeScript/MLIRLogic/MLIRHelper.h" #include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringSet.h" #include #include "llvm/Support/Debug.h" @@ -44,8 +45,10 @@ struct GenericFunctionInfo SourceFile sourceFile; StringRef fileName; - bool processing; - bool processed; + // the instances being generated, by full name: a call of one of them from its own body (or from an + // instance it calls) is recursion and takes its type; any other instance can still be made then + llvm::StringSet<> processing; + bool processed = false; // declared inside a function, whose locals its body may read; otherwise an instantiation sees no // local of the function it is instantiated from (MLIRGenImpl::isolateGeneratedBodies) diff --git a/tslang/lib/TypeScript/MLIRGenGenerics.cpp b/tslang/lib/TypeScript/MLIRGenGenerics.cpp index 0f2ee908b..25a93dc6d 100644 --- a/tslang/lib/TypeScript/MLIRGenGenerics.cpp +++ b/tslang/lib/TypeScript/MLIRGenGenerics.cpp @@ -1058,12 +1058,12 @@ namespace mlirgen genericTypeGenContext.typeParamsWithArgs[typeParam->getName()] = std::make_pair(typeParam, getAnyType()); } - if (functionGenericTypeInfo->processing) + auto [anyFullName, anyName] = getNameOfFunction(functionGenericTypeInfo->functionDeclaration, genericTypeGenContext); + if (functionGenericTypeInfo->processing.contains(anyFullName)) { - auto [fullName, funcName] = getNameOfFunction(functionGenericTypeInfo->functionDeclaration, genericTypeGenContext); - if (auto funcType = lookupFunctionTypeMap(fullName)) + if (auto funcType = lookupFunctionTypeMap(anyFullName)) { - return {mlir::success(), funcType, fullName}; + return {mlir::success(), funcType, anyFullName}; } emitError(location) << "can't instantiate '" << name << "' with 'any' while it is being instantiated"; @@ -1073,10 +1073,10 @@ namespace mlirgen mlir::OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointToStart(theModule.getBody()); - functionGenericTypeInfo->processing = true; + functionGenericTypeInfo->processing.insert(anyFullName); auto [result, funcOp, funcName, isGeneric] = mlirGenFunctionLikeDeclaration(functionGenericTypeInfo->functionDeclaration, genericTypeGenContext); - functionGenericTypeInfo->processing = false; + functionGenericTypeInfo->processing.erase(anyFullName); if (mlir::failed(result) || !funcOp) { return {mlir::failure(), mlir_ts::FunctionType(), ""}; @@ -1244,11 +1244,12 @@ namespace mlirgen if (anyNamedGenericType == IsGeneric::False) { - if (functionGenericTypeInfo->processing) + // a call of the instance being made, from its own body: recursion, which takes the type it + // has so far. A call of another instance (`f` from `f`) makes that one. + auto [fullName, name] = + getNameOfFunction(functionGenericTypeInfo->functionDeclaration, genericTypeGenContext); + if (functionGenericTypeInfo->processing.contains(fullName)) { - auto [fullName, name] = - getNameOfFunction(functionGenericTypeInfo->functionDeclaration, genericTypeGenContext); - auto funcType = lookupFunctionTypeMap(fullName); if (funcType) { @@ -1263,11 +1264,20 @@ namespace mlirgen return {mlir::failure(), mlir_ts::FunctionType(), ""}; } + // `f` calling `f` would make instances without end + if (functionGenericTypeInfo->processing.size() >= MAX_NESTED_GENERIC_INSTANCES) + { + emitError(location) << "can't instantiate '" << functionGenericTypeInfo->name << "': more than " + << MAX_NESTED_GENERIC_INSTANCES + << " of its instances are being made at once, one calling the next"; + return {mlir::failure(), mlir_ts::FunctionType(), ""}; + } + // create new instance of function with TypeArguments - functionGenericTypeInfo->processing = true; + functionGenericTypeInfo->processing.insert(fullName); auto [result, funcOp, funcName, isGeneric] = mlirGenFunctionLikeDeclaration(functionGenericTypeInfo->functionDeclaration, genericTypeGenContext); - functionGenericTypeInfo->processing = false; + functionGenericTypeInfo->processing.erase(fullName); if (mlir::failed(result)) { return {mlir::failure(), mlir_ts::FunctionType(), ""}; diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index ca73a7a41..a2c827f6e 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -5382,6 +5382,9 @@ class MLIRGenImpl mlir::LogicalResult mlirGen(ForStatement forStatementAST, const GenContext &genContext); + // the keys `for...in` walks in an object literal's or a tuple's type (MLIRGenStatements.cpp) + static bool forInFieldKeys(mlir::Type type, SmallVector &keys); + mlir::LogicalResult mlirGen(ForInStatement forInStatementAST, const GenContext &genContext); mlir::LogicalResult mlirGenES3(ForOfStatement forOfStatementAST, mlir::Value exprValue, @@ -6457,6 +6460,15 @@ class MLIRGenImpl if (auto constantOp = leftExpressionValue.getDefiningOp()) { + // every key `for...in` walks in an object literal or a tuple, the position of an unnamed field too + SmallVector fieldKeys; + auto keyAttr = dyn_cast(constantOp.getValue()); + if (keyAttr && forInFieldKeys(rightExpressionValue.getType(), fieldKeys) + && llvm::is_contained(fieldKeys, keyAttr.getValue().str())) + { + return mlirGenBooleanValue(loc(binaryExpressionAST->right), true); + } + auto hasField = !!mth.getFieldTypeByFieldName(rightExpressionValue.getType(), constantOp.getValue()); return mlirGenBooleanValue(loc(binaryExpressionAST->right), hasField); } diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index 7dd888c7d..d1b5704e5 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -1013,6 +1013,74 @@ namespace mlirgen return mlir::success(); } + // The keys `for...in` walks in a value of an object literal's or a tuple's type, in field order: a field's + // name, an unnamed field's position ("0", "1", ...) and an accessor's name once for its get_/set_ pair. + // False when the type has no fields known at compile time. + bool MLIRGenImpl::forInFieldKeys(mlir::Type type, SmallVector &keys) + { + ArrayRef fields; + if (auto tupleType = dyn_cast(type)) + { + fields = tupleType.getFields(); + } + else if (auto constTupleType = dyn_cast(type)) + { + fields = constTupleType.getFields(); + } + else if (auto objectType = dyn_cast(type)) + { + if (auto storageType = dyn_cast(objectType.getStorageType())) + { + fields = storageType.getFields(); + } + else if (auto storageTuple = dyn_cast(objectType.getStorageType())) + { + fields = storageTuple.getFields(); + } + else + { + return false; + } + } + else + { + return false; + } + + for (auto [index, field] : llvm::enumerate(fields)) + { + std::string key; + if (auto name = dyn_cast_or_null(field.id)) + { + key = name.getValue().str(); + if (key.size() > 4 && (StringRef(key).starts_with("get_") || StringRef(key).starts_with("set_"))) + { + key = key.substr(4); + } + } + else if (auto position = dyn_cast_or_null(field.id)) + { + key = std::to_string(position.getValue().getSExtValue()); + } + else if (!field.id) + { + key = std::to_string(index); + } + else + { + // a symbol-named field (`[Symbol.iterator]`) is not walked by for...in + continue; + } + + if (std::find(keys.begin(), keys.end(), key) == keys.end()) + { + keys.push_back(key); + } + } + + return true; + } + mlir::LogicalResult MLIRGenImpl::mlirGen(ForInStatement forInStatementAST, const GenContext &genContext) { SymbolTableScopeT varScope(symbolTable); @@ -1021,6 +1089,68 @@ namespace mlirgen NodeFactory nf(NodeFactoryFlags::None); + // An object literal or a tuple has no keys to find at run time: its fields are known from its type, so the + // loop is made for each of them, with the key a string literal, so that `obj[key]` in the body is that + // field, of its own type: + // for (let .i = 0; .i < N; .i++) { if (.i == 0) { const key = "a"; body } else if (.i == 1) { ... } } + // The loop is still a loop, so `break` and `continue` keep their meaning. + SmallVector fieldKeys; + if (forInFieldKeys(evaluate(forInStatementAST->expression, genContext), fieldKeys)) + { + auto _i = nf.createIdentifier(S(".i")); + NodeArray declarations; + declarations.push_back(nf.createVariableDeclaration(_i, undefined, undefined, nf.createNumericLiteral(S("0")))); + + auto _o = nf.createIdentifier(S(".o")); + auto objectVar = nf.createVariableDeclaration(_o, undefined, undefined, forInStatementAST->expression); + objectVar->internalFlags |= InternalFlags::ForceConstRef; + declarations.push_back(objectVar); + + auto initVars = nf.createVariableDeclarationList(declarations, NodeFlags::Let); + + auto count = nf.createNumericLiteral(stows(std::to_string(fieldKeys.size()))); + auto cond = nf.createBinaryExpression(_i, nf.createToken(SyntaxKind::LessThanToken), count); + auto incr = nf.createPrefixUnaryExpression(nf.createToken(SyntaxKind::PlusPlusToken), _i); + + // the key: the loop's own `const key` / `let key`, or a variable declared before the loop + auto initializer = forInStatementAST->initializer; + auto keyStatement = [&](std::string key) -> ts::Statement { + auto keyLiteral = nf.createStringLiteral(stows(key)); + if (initializer == SyntaxKind::VariableDeclarationList) + { + auto declList = initializer.as(); + auto declared = declList->declarations.front(); + NodeArray keyDeclarations; + keyDeclarations.push_back( + nf.createVariableDeclaration(declared->name, undefined, declared->type, keyLiteral)); + return nf.createVariableStatement( + undefined, nf.createVariableDeclarationList(keyDeclarations, declList->flags)); + } + + return nf.createExpressionStatement(nf.createBinaryExpression( + initializer.as(), nf.createToken(SyntaxKind::EqualsToken), keyLiteral)); + }; + + ts::Statement chain = undefined; + for (auto index = (int)fieldKeys.size() - 1; index >= 0; index--) + { + NodeArray statements; + statements.push_back(keyStatement(fieldKeys[index])); + statements.push_back(forInStatementAST->statement); + auto isThisField = nf.createBinaryExpression(_i, nf.createToken(SyntaxKind::EqualsEqualsEqualsToken), + nf.createNumericLiteral(stows(std::to_string(index)))); + chain = nf.createIfStatement(isThisField, nf.createBlock(statements), chain); + } + + NodeArray body; + if (chain) + { + body.push_back(chain); + } + + return mlirGen(nf.createForStatement(initVars, cond, incr, nf.createBlock(body)), genContext); + } + // init NodeArray declarations; auto _i = nf.createIdentifier(S(".i")); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 75ef177c1..23df1bbb9 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -2518,6 +2518,7 @@ set(TSLANG_CORPUS 00class_constructor_overloads.ts 00callback_result_ignored.ts 00generic_default_no_inference.ts + 00generic_instance_from_instance.ts 00promise_type.ts 00class_deconst.ts 00class_def_constr.ts @@ -2578,6 +2579,7 @@ set(TSLANG_CORPUS 00for_await_yield.ts 00for_await.ts 00for_in.ts + 00for_in_fields.ts 00for_infinite_result.ts 00for_of.ts 00for.ts @@ -4834,6 +4836,18 @@ tslang_add_test(NAME test-compile-own-00-const-interface-of-local COMMAND test-r tslang_add_test(NAME test-jit-own-00-self-assign COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00self_assign.ts") tslang_add_test(NAME test-compile-own-00-self-assign COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00self_assign.ts") +# for...in over an object literal or a tuple walks the fields of its type, and an instance of a generic +# function makes other instances of it while it is being made (JSON.stringify of an object literal or of +# nested arrays). rc and none run them from TSLANG_CORPUS. +tslang_add_test(NAME test-compile-00-for-in-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_in_fields.ts") +tslang_add_test(NAME test-compile-own-00-for-in-fields COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_in_fields.ts") +tslang_add_test(NAME test-jit-00-for-in-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_in_fields.ts") +tslang_add_test(NAME test-jit-own-00-for-in-fields COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_in_fields.ts") +tslang_add_test(NAME test-compile-00-generic-instance-from-instance COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_instance_from_instance.ts") +tslang_add_test(NAME test-compile-own-00-generic-instance-from-instance COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_instance_from_instance.ts") +tslang_add_test(NAME test-jit-00-generic-instance-from-instance COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_instance_from_instance.ts") +tslang_add_test(NAME test-jit-own-00-generic-instance-from-instance COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_instance_from_instance.ts") + # Shared (spec 23): counted handles. These are not in the own_test list, whose # test-own-no-counting check forbids __tslang_inc_ref: a handle is counted under own by design. # own_shared_user_named: a program's own class named Shared wins over the built-in (spec 23.1). diff --git a/tslang/test/tester/tests/00for_in.ts b/tslang/test/tester/tests/00for_in.ts index b46e47194..94e0ec179 100644 --- a/tslang/test/tester/tests/00for_in.ts +++ b/tslang/test/tester/tests/00for_in.ts @@ -1,11 +1,9 @@ function main() { - /* - const object : { name: string, age: number } = { name: "foo", age: 10.0 }; + const record: { name: string, age: number } = { name: "foo", age: 10.0 }; - for (const property in object) { - print(`${property}: ${object[property]}`); - } - */ + for (const property in record) { + print(`${property}: ${record[property]}`); + } const object = [10, 20, 30]; diff --git a/tslang/test/tester/tests/00for_in_fields.ts b/tslang/test/tester/tests/00for_in_fields.ts new file mode 100644 index 000000000..23dbd604a --- /dev/null +++ b/tslang/test/tester/tests/00for_in_fields.ts @@ -0,0 +1,69 @@ +// `for...in` over an object literal or a tuple walks the fields of its type, the key a literal, so +// `obj[key]` is that field with its own type (JSON.stringify of an object literal is made of it). + +function keysOf(value: T): string { + let result = ""; + for (const key in value) { + result += key + "=" + value[key] + ";"; + } + + return result; +} + +function main() { + const person = { name: "Ann", age: 30, nested: { a: true } }; + let keys = ""; + for (const key in person) { + keys += key + ","; + } + + assert(keys == "name,age,nested,", "the fields, in order"); + assert(keysOf({ x: 1, s: "a" }) == "x=1;s=a;", "each field with its own type, in a generic"); + + // a tuple's fields have no names: their keys are their positions, and `t["1"]` and `"1" in t` find them + const tuple = [1, "a"]; + assert(keysOf(tuple) == "0=1;1=a;", "a tuple's positions"); + assert(tuple["1"] == "a", "a position as a string"); + assert("0" in tuple && "1" in tuple && !("2" in tuple), "a position is in a tuple"); + assert("name" in person && !("0" in person), "a name is in an object literal, a position is not"); + + // still a loop: break and continue + let seen = ""; + for (const key in person) { + if (key == "name") { + continue; + } + + if (key == "nested") { + break; + } + + seen += key; + } + + assert(seen == "age", "continue and break"); + + // a key declared before the loop + let last = ""; + for (last in person) { + } + + assert(last == "nested", "a variable declared before the loop"); + + let count = 0; + for (const key in {}) { + count++; + } + + assert(count == 0, "no fields"); + + // an object literal with a method + let withMethod = ""; + for (const key in { k: 2, m() { return 1; } }) { + withMethod += key; + } + + assert(withMethod.length == 2, "a method is a field too"); + + print("done."); +} diff --git a/tslang/test/tester/tests/00generic_instance_from_instance.ts b/tslang/test/tester/tests/00generic_instance_from_instance.ts new file mode 100644 index 000000000..4081b0f81 --- /dev/null +++ b/tslang/test/tester/tests/00generic_instance_from_instance.ts @@ -0,0 +1,42 @@ +// A generic function's instance can make another instance of the same function while it is being made: +// `flatten` calls `flatten`, which calls `flatten`. This failed with +// "not all generic types can be identified" unless the inner instances had been made before, which is how +// `JSON.stringify([[1], [2]])` failed in a program that had not stringified a number first. + +function flatten(value: T): string { + if (typeof value == "array") { + let result = ""; + for (const element of value) { + result += flatten(element); + } + + return result; + } else { + return "<" + value + ">"; + } +} + +// two generic functions that call each other's instances, with a default type parameter +function wrap(value: T): R { + if (typeof value == "array") { + return "[" + each(value) + "]"; + } else { + return "" + value; + } +} + +function each(values: T[]): string { + let result = ""; + for (const value of values) { + result += wrap(value); + } + + return result; +} + +function main() { + assert(flatten([[1], [2, 3]]) == "<1><2><3>", "an instance made from an instance"); + assert(flatten([[["a"]], [["b", "c"]]]) == "", "three deep"); + assert(wrap([[1, 2], [3]]) == "[[12][3]]", "two functions, each making the other's instances"); + print("done."); +} From cc021dc8d77e0cf7f49050e198f1d81d4438dad4 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 12 Nov 2026 21:35:13 +0000 Subject: [PATCH 2/3] for...in over fields: get_ names that are values, arrow functions in the body - A value field named get_x / set_x was taken for an accessor and walked as `x`, which then was not found. Only a function field drops the prefix. - The body is made once for each field, and an arrow function in it was named by its place in the source, so the second copy redefined the first's symbol. A copy's block now adds its key to every name made from a location while it is generated (MLIRHelper::anonymousNameCopy). Co-Authored-By: Claude Opus 5.5 --- .../include/TypeScript/MLIRLogic/MLIRHelper.h | 6 +++ tslang/lib/TypeScript/MLIRGenStatements.cpp | 39 ++++++++++++++++++- tslang/test/tester/tests/00for_in_fields.ts | 19 +++++++++ tslang/ts-new-parser/enums.h | 5 ++- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRHelper.h b/tslang/include/TypeScript/MLIRLogic/MLIRHelper.h index 887156df5..5350d19af 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRHelper.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRHelper.h @@ -251,6 +251,11 @@ class MLIRHelper }); } + // Set while one piece of source is generated more than once in one function - the body of a `for...in` + // over an object literal's fields, once for each field (MLIRGenImpl::mlirGen(ts::Block)): a name made + // from a location (an arrow function's, an object literal's type's) is then one for each copy. + static inline thread_local std::string anonymousNameCopy; + static std::string getAnonymousName(mlir::Location loc, const char *prefix, StringRef fullNamesapceName) { // auto calculate name @@ -259,6 +264,7 @@ class MLIRHelper ssName << fullNamesapceName.str() << "."; ssName << prefix; getAnonymousNameStep(ssName, loc); + ssName << anonymousNameCopy; return ssName.str(); } diff --git a/tslang/lib/TypeScript/MLIRGenStatements.cpp b/tslang/lib/TypeScript/MLIRGenStatements.cpp index d1b5704e5..a0d97d4ba 100644 --- a/tslang/lib/TypeScript/MLIRGenStatements.cpp +++ b/tslang/lib/TypeScript/MLIRGenStatements.cpp @@ -173,6 +173,36 @@ namespace mlirgen mlir::LogicalResult MLIRGenImpl::mlirGen(ts::Block blockAST, const GenContext &genContext, int skipStatements) { + // one field's copy of a `for...in` body (mlirGen(ForInStatement)): its arrow functions and the like are its + // own, named for its key, which its first statement sets + if ((blockAST->internalFlags & InternalFlags::ForInFieldCopy) == InternalFlags::ForInFieldCopy) + { + auto copyName = std::string(".k"); + auto keyStatement = blockAST->statements.front(); + Expression keyLiteral = undefined; + if (keyStatement == SyntaxKind::VariableStatement) + { + keyLiteral = keyStatement.as()->declarationList->declarations.front()->initializer; + } + else if (keyStatement == SyntaxKind::ExpressionStatement) + { + keyLiteral = keyStatement.as()->expression.as()->right; + } + + if (keyLiteral == SyntaxKind::StringLiteral) + { + copyName += convertWideToUTF8(keyLiteral.as()->text); + } + + MLIRValueGuard copyGuard(MLIRHelper::anonymousNameCopy); + MLIRHelper::anonymousNameCopy += copyName; + + blockAST->internalFlags &= ~InternalFlags::ForInFieldCopy; + auto result = mlirGen(blockAST, genContext, skipStatements); + blockAST->internalFlags |= InternalFlags::ForInFieldCopy; + return result; + } + // A `using` here needs disposal on the unwind path too, and only a TryOp has a // landing pad to run that from - see mlirGenBlockWithUnwindCleanup and // blockDeclaresUsing. Most blocks qualify: a function's own body, an if/loop body, a @@ -1053,7 +1083,10 @@ namespace mlirgen if (auto name = dyn_cast_or_null(field.id)) { key = name.getValue().str(); - if (key.size() > 4 && (StringRef(key).starts_with("get_") || StringRef(key).starts_with("set_"))) + // an accessor is a function field named get_x / set_x; a value field may be named so too + auto isAccessor = isa(field.type) + && key.size() > 4 && (StringRef(key).starts_with("get_") || StringRef(key).starts_with("set_")); + if (isAccessor) { key = key.substr(4); } @@ -1139,7 +1172,9 @@ namespace mlirgen statements.push_back(forInStatementAST->statement); auto isThisField = nf.createBinaryExpression(_i, nf.createToken(SyntaxKind::EqualsEqualsEqualsToken), nf.createNumericLiteral(stows(std::to_string(index)))); - chain = nf.createIfStatement(isThisField, nf.createBlock(statements), chain); + auto fieldBlock = nf.createBlock(statements); + fieldBlock->internalFlags |= InternalFlags::ForInFieldCopy; + chain = nf.createIfStatement(isThisField, fieldBlock, chain); } NodeArray body; diff --git a/tslang/test/tester/tests/00for_in_fields.ts b/tslang/test/tester/tests/00for_in_fields.ts index 23dbd604a..17f2a3166 100644 --- a/tslang/test/tester/tests/00for_in_fields.ts +++ b/tslang/test/tester/tests/00for_in_fields.ts @@ -65,5 +65,24 @@ function main() { assert(withMethod.length == 2, "a method is a field too"); + // an accessor is one key; a value field named like an accessor's storage is its own name (the accessor + // comes after the value fields, not where it is written) + let accessors = ""; + for (const key in { v: 3, get x() { return 1; }, set x(n: number) { }, get_started: true }) { + accessors += "," + key; + } + + assert(accessors.length == ",v,x,get_started".length && accessors.indexOf(",x") >= 0 + && accessors.indexOf(",get_started") >= 0, "accessors and get_ names"); + + // the body is made once for each field: an arrow function in it is one for each too + let captured = ""; + for (const key in person) { + const keyOf = () => key; + captured += keyOf(); + } + + assert(captured == "nameagenested", "an arrow function in the body"); + print("done."); } diff --git a/tslang/ts-new-parser/enums.h b/tslang/ts-new-parser/enums.h index 418176301..1fcd47386 100644 --- a/tslang/ts-new-parser/enums.h +++ b/tslang/ts-new-parser/enums.h @@ -570,7 +570,10 @@ enum class InternalFlags : number GeneratorDoneValue = 1 << 14, // an object literal a variable declared with no type is given (`const o = { w: ... }`): its this-free arrows // are closures (markClosureMembers) - UntypedVariableInitializer = 1 << 15 + UntypedVariableInitializer = 1 << 15, + // the body of a `for...in` over an object literal's fields made for one field: its first statement gives + // the key, which names what it makes from a location (MLIRHelper::anonymousNameCopy) + ForInFieldCopy = 1 << 16 }; ENUM_OPS(InternalFlags) From 4adeb8fd2be38a05e2afdbf5ef406185b8c9b4b0 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Thu, 12 Nov 2026 21:38:32 +0000 Subject: [PATCH 3/3] 00for_in_fields.ts: no string methods (tester tests build without the default library) Co-Authored-By: Claude Opus 5.5 --- tslang/test/tester/tests/00for_in_fields.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tslang/test/tester/tests/00for_in_fields.ts b/tslang/test/tester/tests/00for_in_fields.ts index 17f2a3166..837bc39cf 100644 --- a/tslang/test/tester/tests/00for_in_fields.ts +++ b/tslang/test/tester/tests/00for_in_fields.ts @@ -67,13 +67,16 @@ function main() { // an accessor is one key; a value field named like an accessor's storage is its own name (the accessor // comes after the value fields, not where it is written) - let accessors = ""; + let accessorKeys = 0; + let found = 0; for (const key in { v: 3, get x() { return 1; }, set x(n: number) { }, get_started: true }) { - accessors += "," + key; + accessorKeys++; + if (key == "v" || key == "x" || key == "get_started") { + found++; + } } - assert(accessors.length == ",v,x,get_started".length && accessors.indexOf(",x") >= 0 - && accessors.indexOf(",get_started") >= 0, "accessors and get_ names"); + assert(accessorKeys == 3 && found == 3, "accessors and get_ names"); // the body is made once for each field: an arrow function in it is one for each too let captured = "";