From da06b90b1cd48bd2f996887a9a13c23fbcb21bde Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 13:36:45 +0000 Subject: [PATCH] Infer a generic class's type from overloaded and rest constructors; CI tests the default library under every model `new Array(10, 20, 30)` against DefaultLib's Array with three constructors (`data: T[]`, `count: int`, `...args: T[]`) stayed the unspecialized Array: inferClassTypeFromConstructorArguments gave up on any class with more than one constructor, and skipped rest parameters. It now tries the constructors that can take that many arguments in declaration order, takes T from every argument a rest parameter collects, and the first that gives every type argument answers. CI built the default library for gc, rc, none and own but tested only gc: the Test Default Library steps (Windows and Linux) now run tests.ps1 / tests.sh for each model, every model before a failure fails the step. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/create-release.yml | 21 +++- tslang/lib/TypeScript/MLIRGenGenerics.cpp | 119 ++++++++++++------ .../00generic_class_constructor_inference.ts | 22 ++++ 3 files changed, 119 insertions(+), 43 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 56a9efcbf..7dc02f6ed 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -188,6 +188,8 @@ jobs: run: ctest -C ${{ env.BUILD_TYPE }} -R gc-defaultlib-collector --output-on-failure shell: pwsh + # Every memory model the library is built for, each in its own pwsh: tests.ps1 reads the + # library paths once, at its start. All of them run before a failure fails the step. - name: Test Default Library continue-on-error: false working-directory: ${{github.workspace}}/TypeScriptCompilerDefaultLib @@ -197,8 +199,13 @@ jobs: LLVM_LIB_PATH: ${{github.workspace}}/3rdParty/llvm/x64/release/lib TSLANG_LIB_PATH: ${{github.workspace}}/__build/tslang/msbuild/x64/release/lib run: | - .\tests.ps1 - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $failed = @() + foreach ($model in "gc", "rc", "none", "own") { + Write-Host "=== -mm=$model ===" + pwsh -NoProfile -File .\tests.ps1 -Model $model + if ($LASTEXITCODE -ne 0) { $failed += $model } + } + if ($failed.Count -gt 0) { Write-Host "Failed models: $($failed -join ', ')"; exit 1 } shell: pwsh # Android (API 29, arm64-v8a, armeabi-v7a and x86_64): the collector, the async runtime and the default @@ -424,6 +431,7 @@ jobs: TSLANG_REQUIRE_DEFAULT_LIB: 1 run: ctest -C ${{ env.BUILD_TYPE }} -R gc-defaultlib-collector --output-on-failure + # Every memory model, as on Windows: all of them run before a failure fails the step. - name: Test Default Library continue-on-error: false working-directory: ${{github.workspace}}/TypeScriptCompilerDefaultLib @@ -433,7 +441,14 @@ jobs: GC_LIB_PATH: ${{github.workspace}}/__build/gc/ninja/release LLVM_LIB_PATH: ${{github.workspace}}/3rdParty/llvm/release/lib TSLANG_LIB_PATH: ${{github.workspace}}/__build/tslang/ninja/release/lib - run: chmod +x ./tests.sh; ./tests.sh + run: | + chmod +x ./tests.sh + failed="" + for model in gc rc none own; do + echo "=== -mm=$model ===" + ./tests.sh "$model" || failed="$failed $model" + done + if [ -n "$failed" ]; then echo "Failed models:$failed"; exit 1; fi - name: Create Tar.GZ of Linux Asset working-directory: ${{github.workspace}}/__build diff --git a/tslang/lib/TypeScript/MLIRGenGenerics.cpp b/tslang/lib/TypeScript/MLIRGenGenerics.cpp index c2a5c0e1f..e092d15f9 100644 --- a/tslang/lib/TypeScript/MLIRGenGenerics.cpp +++ b/tslang/lib/TypeScript/MLIRGenGenerics.cpp @@ -1384,27 +1384,24 @@ namespace mlirgen // was "missing type arguments". A type parameter the arguments do not reach keeps its default; // when one has neither, or anything is still generic, there is no answer here and the caller // goes on as before. + // With overloads (`Array`: `(data: T[])`, `(count: int)`, `(...args: T[])`), the constructors + // that can take that many arguments are tried in declaration order, and the first that gives + // every type argument answers: `new Array(10, 20, 30)` is Array by its rest parameter. mlir_ts::ClassType MLIRGenImpl::inferClassTypeFromConstructorArguments(mlir::Location location, GenericClassInfo::TypePtr genericClassInfo, NodeArray arguments, const GenContext &genContext) { - // one constructor: with overloads, which one the arguments pick is a question for the call - ConstructorDeclaration constructorDeclaration; + SmallVector constructorDeclarations; for (auto member : genericClassInfo->classDeclaration->members) { if (member == SyntaxKind::Constructor && !hasModifier(member, SyntaxKind::StaticKeyword)) { - if (constructorDeclaration) - { - return mlir_ts::ClassType(); - } - - constructorDeclaration = member.as(); + constructorDeclarations.push_back(member.as()); } } - if (!constructorDeclaration) + if (constructorDeclarations.empty()) { return mlir_ts::ClassType(); } @@ -1412,10 +1409,12 @@ namespace mlirgen // the arguments' types, in the caller's scope; a function argument's parameters are typed // by the constructor's, which is what is being worked out, so it tells nothing SmallVector argumentTypes; + auto hasSpread = false; for (auto argument : arguments) { if (argument == SyntaxKind::SpreadElement) { + hasSpread = true; break; } @@ -1436,57 +1435,97 @@ namespace mlirgen templateGenContext.typeParamsWithArgs[typeParam->getName()] = {typeParam, getNamedGenericType(typeParam->getName())}; } - StringMap inferredTypes; - auto parameters = constructorDeclaration->parameters; - for (auto [index, argumentType] : enumerate(argumentTypes)) - { - if (index >= parameters.size()) + // the type arguments one constructor's parameters give, or none + auto inferFromConstructor = [&](ConstructorDeclaration constructorDeclaration) -> SmallVector { + auto parameters = constructorDeclaration->parameters; + + // can it take that many arguments: a spread's count is not known + auto hasRest = parameters.size() > 0 && parameters[parameters.size() - 1]->dotDotDotToken; + auto required = 0u; + for (auto parameter : parameters) { - break; + if (!parameter->dotDotDotToken && !parameter->initializer && !parameter->questionToken) + { + required++; + } } - auto parameter = parameters[index]; - if (parameter->dotDotDotToken || !parameter->type || !argumentType) + if (!hasSpread && (arguments.size() < required || (!hasRest && arguments.size() > parameters.size()))) { - continue; + return {}; } - if (auto parameterType = getType(parameter->type, templateGenContext)) + StringMap inferredTypes; + for (auto [index, argumentType] : enumerate(argumentTypes)) { - inferType(location, parameterType, argumentType, inferredTypes, templateGenContext); + if (index >= parameters.size() && !hasRest) + { + break; + } + + // the arguments a rest parameter `...args: T[]` collects each give its element type + auto parameter = parameters[std::min(index, parameters.size() - 1)]; + if (!parameter->type || !argumentType) + { + continue; + } + + auto parameterType = getType(parameter->type, templateGenContext); + if (parameterType && parameter->dotDotDotToken) + { + auto arrayType = dyn_cast(parameterType); + parameterType = arrayType ? arrayType.getElementType() : mlir::Type(); + } + + if (parameterType) + { + inferType(location, parameterType, argumentType, inferredTypes, templateGenContext); + } } - } - // in declaration order, up to the first the arguments do not reach; the rest must have defaults - SmallVector typeArguments; - for (auto &typeParam : genericClassInfo->typeParams) - { - auto found = inferredTypes.find(typeParam->getName()); - if (found == inferredTypes.end()) + // in declaration order, up to the first the arguments do not reach; the rest must have defaults + SmallVector typeArguments; + for (auto &typeParam : genericClassInfo->typeParams) { - break; + auto found = inferredTypes.find(typeParam->getName()); + if (found == inferredTypes.end()) + { + break; + } + + auto type = mth.wideStorageType(found->getValue()); + if (mth.isGenericType(type)) + { + return {}; + } + + typeArguments.push_back(type); } - auto type = mth.wideStorageType(found->getValue()); - if (mth.isGenericType(type)) + for (auto index = typeArguments.size(); index < genericClassInfo->typeParams.size(); index++) { - return mlir_ts::ClassType(); + if (!genericClassInfo->typeParams[index]->hasDefault()) + { + return {}; + } } - typeArguments.push_back(type); - } + return typeArguments; + }; - if (typeArguments.empty()) + SmallVector typeArguments; + for (auto constructorDeclaration : constructorDeclarations) { - return mlir_ts::ClassType(); + typeArguments = inferFromConstructor(constructorDeclaration); + if (!typeArguments.empty()) + { + break; + } } - for (auto index = typeArguments.size(); index < genericClassInfo->typeParams.size(); index++) + if (typeArguments.empty()) { - if (!genericClassInfo->typeParams[index]->hasDefault()) - { - return mlir_ts::ClassType(); - } + return mlir_ts::ClassType(); } auto [result, specType] = diff --git a/tslang/test/tester/tests/00generic_class_constructor_inference.ts b/tslang/test/tester/tests/00generic_class_constructor_inference.ts index 7e577f722..af22a734e 100644 --- a/tslang/test/tester/tests/00generic_class_constructor_inference.ts +++ b/tslang/test/tester/tests/00generic_class_constructor_inference.ts @@ -40,6 +40,19 @@ class Applied { } } +// overloads: the first constructor that can take the arguments and gives T answers; a rest +// parameter takes T from every argument it collects (any overload made it "missing type arguments") +class List { + items: T[]; + constructor(data: T[]) { + this.items = data; + } + + constructor(...args: T[]) { + this.items = args; + } +} + function boxOf(items: T[]) { return new Box(items); } @@ -92,5 +105,14 @@ function main() { assert(firstValue([["one", 1], ["two", 2]]) * 10 == 10); + const fromArray = new List([1, 2, 3]); + assert(fromArray.items.length == 3 && fromArray.items[2] + 1 == 4); + + const fromRest = new List(10, 20, 30); + assert(fromRest.items.length == 3 && fromRest.items[1] + 1 == 21); + + const fromStrings = new List("a", "b"); + assert(fromStrings.items[1] + "!" == "b!"); + print("done."); }