diff --git a/tslang/lib/TypeScript/MLIRGenClasses.cpp b/tslang/lib/TypeScript/MLIRGenClasses.cpp index f49da68ca..baeddaf80 100644 --- a/tslang/lib/TypeScript/MLIRGenClasses.cpp +++ b/tslang/lib/TypeScript/MLIRGenClasses.cpp @@ -1525,18 +1525,98 @@ genContext); NodeFactory nf(NodeFactoryFlags::None); + // errors in the generated code are reported at the class + auto atClass = [&](auto node) { + node->pos = classDeclarationAST->pos; + node->_end = classDeclarationAST->_end; + return node; + }; + + NodeArray parameters; NodeArray statements; if (!newClassPtr->baseClasses.empty()) { - auto superExpr = nf.createToken(SyntaxKind::SuperKeyword); - auto callSuper = nf.createCallExpression(superExpr, undefined, undefined); - statements.push_back(nf.createExpressionStatement(callSuper)); + // TypeScript's implicit `constructor(...args) { super(...args); }`: it takes the + // parameters of the base constructor, typed as its function is (a generic base's + // already specialized), and passes them on; the base applies its defaults + mlir_ts::FunctionType baseConstructorType; + for (auto baseClass = newClassPtr->baseClasses.front(); baseClass; + baseClass = baseClass->baseClasses.empty() ? ClassInfo::TypePtr() : baseClass->baseClasses.front()) + { + if (baseClass->hasConstructor) + { + auto index = baseClass->getMethodIndex(CONSTRUCTOR_NAME); + if (index >= 0) + { + baseConstructorType = baseClass->methods[index].funcType; + } + + break; + } + } + + NodeArray superArguments; + if (baseConstructorType) + { + auto inputs = baseConstructorType.getInputs(); + // the first input is `this` + for (auto index = 1u; index < inputs.size(); index++) + { + auto paramType = inputs[index]; + auto isRest = baseConstructorType.isVarArg() && index == inputs.size() - 1; + auto isOptional = false; + if (auto optionalType = dyn_cast(paramType)) + { + paramType = optionalType.getElementType(); + isOptional = true; + } + + // not a name the field initializers, run in this constructor, could mean + auto paramName = ".arg" + std::to_string(index - 1); + + std::string paramTypeAlias; + paramTypeAlias += "."; + paramTypeAlias += newClassPtr->fullName.str(); + paramTypeAlias += "."; + paramTypeAlias += CONSTRUCTOR_NAME; + paramTypeAlias += paramName; + getTypeAliasMap()[paramTypeAlias] = { paramType, undefined }; + + DotDotDotToken dotDotDotToken; + if (isRest) + { + dotDotDotToken = nf.createToken(SyntaxKind::DotDotDotToken); + } + + QuestionToken questionToken; + if (isOptional) + { + questionToken = nf.createToken(SyntaxKind::QuestionToken); + } + + parameters.push_back(atClass(nf.createParameterDeclaration( + undefined, dotDotDotToken, nf.createIdentifier(stows(paramName)), questionToken, + nf.createTypeReferenceNode(nf.createIdentifier(stows(paramTypeAlias)), undefined)))); + + Expression argument = atClass(nf.createIdentifier(stows(paramName))); + if (isRest) + { + argument = atClass(nf.createSpreadElement(argument)); + } + + superArguments.push_back(argument); + } + } + + auto superExpr = atClass(nf.createToken(SyntaxKind::SuperKeyword)); + auto callSuper = atClass(nf.createCallExpression(superExpr, undefined, superArguments)); + statements.push_back(atClass(nf.createExpressionStatement(callSuper))); } - auto body = nf.createBlock(statements, /*multiLine*/ false); + auto body = atClass(nf.createBlock(statements, /*multiLine*/ false)); - auto generatedConstructor = nf.createConstructorDeclaration(undefined, undefined, body); + auto generatedConstructor = atClass(nf.createConstructorDeclaration(undefined, parameters, body)); newClassPtr->extraMembers.push_back(generatedConstructor); } @@ -2806,6 +2886,14 @@ genContext); propertyDeclaration->initializer); auto expr_statement = nf.createExpressionStatement(_this_name_equal); + // errors in the assignment (the initializer's cast to the field type) are reported at + // the field, not at the start of the file + for (Node node : {Node(_this), Node(_name), Node(_this_name), Node(_this_name_equal), Node(expr_statement)}) + { + node->pos = propertyDeclaration->pos; + node->_end = propertyDeclaration->_end; + } + // NOTE: upward mailbox: drained when the constructor body is generated - see A7 const_cast(genContext).generatedStatements.push_back(expr_statement.as()); } diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index f76a1d590..8c7532eab 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1080,7 +1080,28 @@ namespace mlirgen { offsetArgs = 2; } - if (mlir::failed(mlirGenOperands(callExpression->arguments, operands, funcResult.getType(), genContext, offsetArgs, noReceiverTypesForGenericCall))) + + // `super(...)`: the callee is the base class, and the arguments are its constructor's. Without + // the constructor's parameters a spread had no count to fill and passed nothing: `super(a, ...r)` + // gave the base an empty rest parameter. + auto operandsFuncType = funcResult.getType(); + if (auto classStorageType = dyn_cast(funcType)) + { + for (auto classInfo = getClassInfoByFullName(classStorageType.getName().getValue()); classInfo; + classInfo = classInfo->baseClasses.empty() ? ClassInfo::TypePtr() : classInfo->baseClasses.front()) + { + auto index = classInfo->getMethodIndex(CONSTRUCTOR_NAME); + if (index >= 0) + { + operandsFuncType = classInfo->methods[index].funcType; + // after `this` + offsetArgs = 1; + break; + } + } + } + + if (mlir::failed(mlirGenOperands(callExpression->arguments, operands, operandsFuncType, genContext, offsetArgs, noReceiverTypesForGenericCall))) { return mlir::failure(); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index f1d97fc0d..a3d505115 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -527,6 +527,7 @@ tslang_add_test(NAME test-compile-00-class-accessor-2 COMMAND test-runner "${PRO tslang_add_test(NAME test-compile-00-class-accessor-super COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_super.ts") tslang_add_test(NAME test-compile-00-class-accessor-virtual COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_virtual.ts") tslang_add_test(NAME test-compile-00-class-super COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_super.ts") +tslang_add_test(NAME test-compile-00-class-implicit-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_implicit_constructor.ts") tslang_add_test(NAME test-compile-00-class-super-static COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_super_static.ts") tslang_add_test(NAME test-compile-00-class-default-constructor COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_def_constr.ts") tslang_add_test(NAME test-compile-00-class-constructor-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_constr_fields.ts") @@ -1055,6 +1056,7 @@ tslang_add_test(NAME test-jit-00-class-accessor-2 COMMAND test-runner -jit "${PR tslang_add_test(NAME test-jit-00-class-accessor-super COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_super.ts") tslang_add_test(NAME test-jit-00-class-accessor-virtual COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_virtual.ts") tslang_add_test(NAME test-jit-00-class-super COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_super.ts") +tslang_add_test(NAME test-jit-00-class-implicit-constructor COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_implicit_constructor.ts") tslang_add_test(NAME test-jit-00-class-super-static COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_super_static.ts") tslang_add_test(NAME test-jit-00-class-default-constructor COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_def_constr.ts") tslang_add_test(NAME test-jit-00-class-static-constructor COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_static_constr.ts") @@ -1837,6 +1839,7 @@ set(TSLANG_CORPUS 00class_structural_extends.ts 00class_super_static.ts 00class_super.ts + 00class_implicit_constructor.ts 00class_symbol_iterator.ts 00class_var_args.ts 00class_virtual_call.ts @@ -2845,6 +2848,23 @@ set_tests_properties(test-compile-diagnostic-location-generated-cast PROPERTIES PASS_REGULAR_EXPRESSION "generated_cast\\.ts:6:23: error: can't cast from 'null' to 'string'" FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed|generated_cast\\.ts:1:") +# An error in a field initializer, or in a derived class's implicit constructor, is reported at the +# field or the class - not at 1:1, where the code the compiler generates for them had put it. +add_test(NAME test-compile-diagnostic-location-field-initializer + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/diagnostic-location/field_initializer.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/diagnostic-location-field-initializer.obj") +set_tests_properties(test-compile-diagnostic-location-field-initializer + PROPERTIES PASS_REGULAR_EXPRESSION "field_initializer\\.ts:6:5: error: can't cast from 'null' to 'Reference'" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed|field_initializer\\.ts:1:") +add_test(NAME test-compile-diagnostic-location-implicit-constructor + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/diagnostic-location/implicit_constructor.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/diagnostic-location-implicit-constructor.obj") +set_tests_properties(test-compile-diagnostic-location-implicit-constructor + PROPERTIES PASS_REGULAR_EXPRESSION "implicit_constructor\\.ts:9:1: error: Class constructor is not accessable" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed|implicit_constructor\\.ts:1:") + # A call to the collector's API (GC_gcollect, or the weak-link functions DefaultLib's WeakRef uses) # is an error under every model but gc, which alone links the collector - it used to reach the # linker as an unresolved symbol. Under gc the same file compiles. diff --git a/tslang/test/tester/diagnostic-location/field_initializer.ts b/tslang/test/tester/diagnostic-location/field_initializer.ts new file mode 100644 index 000000000..81709f2e3 --- /dev/null +++ b/tslang/test/tester/diagnostic-location/field_initializer.ts @@ -0,0 +1,7 @@ +// An error in a field's initializer was reported at the start of the file: the assignment the +// compiler generates for it in the constructor had no position. + +class Args { + count = 1; + protected argv: Ref = null; +} diff --git a/tslang/test/tester/diagnostic-location/implicit_constructor.ts b/tslang/test/tester/diagnostic-location/implicit_constructor.ts new file mode 100644 index 000000000..ba12e6d55 --- /dev/null +++ b/tslang/test/tester/diagnostic-location/implicit_constructor.ts @@ -0,0 +1,11 @@ +// An error in a derived class's implicit constructor was reported at the start of the file. +// Here the base's constructor is private, so the implicit one can't call it. + +class Base { + private constructor(protected a: int) { + } +} + +class Derived extends Base { + extra = 1; +} diff --git a/tslang/test/tester/tests/00class_implicit_constructor.ts b/tslang/test/tester/tests/00class_implicit_constructor.ts new file mode 100644 index 000000000..924ff8c3e --- /dev/null +++ b/tslang/test/tester/tests/00class_implicit_constructor.ts @@ -0,0 +1,103 @@ +// regression test: a derived class without a constructor gets TypeScript's implicit one, +// `constructor(...args) { super(...args); }`. +// +// bugs found and fixed: +// - a derived class with field initializers (and no constructor) was given a generated +// `constructor() { super(); }`, so `new Derived(2, 3)` failed with "Expected 1-2 arguments, +// but got 0", reported at line 1. It now takes the base constructor's parameters (optional +// ones and a rest parameter included) and passes them on. +// - `super(a, ...rest)` passed an empty rest parameter: the arguments of a super call were +// generated without the constructor's parameters, so a spread had nothing to fill. + +class Base { + constructor(protected a: int, protected b: int = 10, public c?: string, ...rest: int[]) { + this.restLength = rest.length; + } + + restLength: int; + + sum(): int { + return this.a + this.b; + } +} + +class Derived extends Base { + extra = 2; +} + +// the implicit constructor of a class whose base has one too +class MoreDerived extends Derived { + more = 3; +} + +class SpreadSuper extends Base { + constructor(a: int, ...rest: int[]) { + super(a, 1, "s", ...rest); + } +} + +class Generic { + constructor(public v: T) { + } +} + +class FromSpecialized extends Generic { + w = 5; +} + +class FromGeneric extends Generic { + w = 6; +} + +class NoConstructorBase { + x = 1; +} + +class NoConstructorDerived extends NoConstructorBase { + y = 2; +} + +// a field initializer runs in the implicit constructor, and still sees the outer `arg0` +const arg0 = 99; +class Shadow extends Base { + z = arg0; +} + +function main() { + const d = new Derived(2, 3, "s", 7, 8); + assert(d.sum() == 5); + assert(d.extra == 2); + assert(d.c == "s"); + assert(d.restLength == 2); + + // the base's default and optional parameters + const d2 = new Derived(4); + assert(d2.sum() == 14); + assert(d2.c == undefined); + assert(d2.restLength == 0); + + const m = new MoreDerived(1, 2, "t", 9); + assert(m.sum() == 3); + assert(m.extra == 2); + assert(m.more == 3); + assert(m.restLength == 1); + + const s = new SpreadSuper(1, 7, 8, 9); + assert(s.restLength == 3); + + const f = new FromSpecialized("hello"); + assert(f.v == "hello"); + assert(f.w == 5); + + const g = new FromGeneric(1.5); + assert(g.v == 1.5); + assert(g.w == 6); + + const n = new NoConstructorDerived(); + assert(n.x == 1); + assert(n.y == 2); + + assert(new Shadow(1).z == 99); + + print("done."); +}