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/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/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..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 @@ -1013,6 +1043,77 @@ 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(); + // 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); + } + } + 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 +1122,70 @@ 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)))); + auto fieldBlock = nf.createBlock(statements); + fieldBlock->internalFlags |= InternalFlags::ForInFieldCopy; + chain = nf.createIfStatement(isThisField, fieldBlock, 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..837bc39cf --- /dev/null +++ b/tslang/test/tester/tests/00for_in_fields.ts @@ -0,0 +1,91 @@ +// `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"); + + // 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 accessorKeys = 0; + let found = 0; + for (const key in { v: 3, get x() { return 1; }, set x(n: number) { }, get_started: true }) { + accessorKeys++; + if (key == "v" || key == "x" || key == "get_started") { + found++; + } + } + + 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 = ""; + 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/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."); +} 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)