Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
119 changes: 79 additions & 40 deletions tslang/lib/TypeScript/MLIRGenGenerics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1384,38 +1384,37 @@ 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<T>`: `(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<si32> by its rest parameter.
mlir_ts::ClassType MLIRGenImpl::inferClassTypeFromConstructorArguments(mlir::Location location,
GenericClassInfo::TypePtr genericClassInfo,
NodeArray<Expression> arguments,
const GenContext &genContext)
{
// one constructor: with overloads, which one the arguments pick is a question for the call
ConstructorDeclaration constructorDeclaration;
SmallVector<ConstructorDeclaration> constructorDeclarations;
for (auto member : genericClassInfo->classDeclaration->members)
{
if (member == SyntaxKind::Constructor && !hasModifier(member, SyntaxKind::StaticKeyword))
{
if (constructorDeclaration)
{
return mlir_ts::ClassType();
}

constructorDeclaration = member.as<ConstructorDeclaration>();
constructorDeclarations.push_back(member.as<ConstructorDeclaration>());
}
}

if (!constructorDeclaration)
if (constructorDeclarations.empty())
{
return mlir_ts::ClassType();
}

// 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<mlir::Type> argumentTypes;
auto hasSpread = false;
for (auto argument : arguments)
{
if (argument == SyntaxKind::SpreadElement)
{
hasSpread = true;
break;
}

Expand All @@ -1436,57 +1435,97 @@ namespace mlirgen
templateGenContext.typeParamsWithArgs[typeParam->getName()] = {typeParam, getNamedGenericType(typeParam->getName())};
}

StringMap<mlir::Type> 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<mlir::Type> {
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<mlir::Type> 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<size_t>(index, parameters.size() - 1)];
if (!parameter->type || !argumentType)
{
continue;
}

auto parameterType = getType(parameter->type, templateGenContext);
if (parameterType && parameter->dotDotDotToken)
{
auto arrayType = dyn_cast<mlir_ts::ArrayType>(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<mlir::Type> 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<mlir::Type> 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<mlir::Type> 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] =
Expand Down
22 changes: 22 additions & 0 deletions tslang/test/tester/tests/00generic_class_constructor_inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ class Applied<T> {
}
}

// 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<T> {
items: T[];
constructor(data: T[]) {
this.items = data;
}

constructor(...args: T[]) {
this.items = args;
}
}

function boxOf<T>(items: T[]) {
return new Box(items);
}
Expand Down Expand Up @@ -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.");
}
Loading