From 40e5145311efce50cffd1ad5da44c5f0aa3cd8c8 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 28 Oct 2026 18:54:43 +0000 Subject: [PATCH] A set accessor's untyped parameter takes its get accessor's type; a setter gets the value as the type it takes `class C { f = 1; get prop() { return this.f; } set prop(v) { ... } }` warned "type for parameter 'v' is any" and `c.prop = 5` then crashed (0xC0000005) at run time. Even `set prop(v: any)` crashed the same way: an assignment through an accessor cast the value to the getter's type (i32) and handed it to a setter that takes `any`, with no conversion. - mlirGenParameters: a class set accessor's parameter with no type and no initializer takes what the get accessor of the same name returns (getPairedGetAccessorType): its written type, else the type of the registered get accessor. When the get accessor comes after the set accessor and is not registered yet, the set accessor is left unresolved and mlirGenClassMembers retries it. With no get accessor it is `any`, as before. - mlirGenSaveLogic: an assignment through any accessor op casts the value to the set accessor's last parameter, not to the accessor's (getter's) type. Test: 00class_accessor_untyped_setter (getter first, setter first, typed getter, static pair, `v: any` setter). Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenFunctions.cpp | 85 +++++++++++++++++++ tslang/lib/TypeScript/MLIRGenImpl.h | 41 +++++++-- tslang/test/tester/CMakeLists.txt | 3 + .../tests/00class_accessor_untyped_setter.ts | 57 +++++++++++++ 4 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 tslang/test/tester/tests/00class_accessor_untyped_setter.ts diff --git a/tslang/lib/TypeScript/MLIRGenFunctions.cpp b/tslang/lib/TypeScript/MLIRGenFunctions.cpp index 648d7a84a..8a113390e 100644 --- a/tslang/lib/TypeScript/MLIRGenFunctions.cpp +++ b/tslang/lib/TypeScript/MLIRGenFunctions.cpp @@ -307,6 +307,22 @@ namespace mlirgen LLVM_DEBUG(dbgs() << "\n!! binding param " << namePtr << " is type " << type << "\n"); } + // a set accessor's value without a type is of the type its get accessor returns + if (mth.isNoneType(type) && kind == SyntaxKind::SetAccessor && !typeParameter && !initializer) + { + auto [result, getAccessorType] = getPairedGetAccessorType(parametersContextAST, genContext); + if (mlir::failed(result)) + { + return {mlir::failure(), isGenericTypes, params}; + } + + if (getAccessorType) + { + type = getAccessorType; + isGenericTypes |= mth.isGenericType(type); + } + } + if (mth.isNoneType(type)) { if (!typeParameter && !initializer) @@ -373,6 +389,75 @@ namespace mlirgen return {mlir::success(), isGenericTypes, params}; } + // the type a class's set accessor takes when its parameter has none: what the get accessor of the + // same name returns. No type when the class has no such get accessor; a failure while the get + // accessor, declared after the set accessor, is not resolved yet (mlirGenClassMembers retries it). + std::tuple MLIRGenImpl::getPairedGetAccessorType( + SignatureDeclarationBase setAccessorAST, const GenContext &genContext) + { + auto parent = setAccessorAST->parent; + if (parent != SyntaxKind::ClassDeclaration && parent != SyntaxKind::ClassExpression) + { + return {mlir::success(), mlir::Type()}; + } + + auto [name, nameResult] = getNameForMethod(setAccessorAST, genContext); + if (!nameResult || name.empty()) + { + return {mlir::success(), mlir::Type()}; + } + + auto isStatic = hasModifier(setAccessorAST, SyntaxKind::StaticKeyword); + FunctionLikeDeclarationBase getAccessorAST; + for (auto &classMember : parent.as()->members) + { + if (classMember != SyntaxKind::GetAccessor || hasModifier(classMember, SyntaxKind::StaticKeyword) != isStatic) + { + continue; + } + + auto getAccessor = classMember.as(); + auto [getName, getNameResult] = getNameForMethod(getAccessor, genContext); + if (getNameResult && getName == name) + { + getAccessorAST = getAccessor; + break; + } + } + + if (!getAccessorAST) + { + return {mlir::success(), mlir::Type()}; + } + + if (getAccessorAST->type) + { + auto type = getType(getAccessorAST->type, genContext); + return {type ? mlir::success() : mlir::failure(), type}; + } + + // the type the get accessor's body gives it, once the get accessor is registered + if (genContext.thisClassType) + { + if (auto classInfo = getClassInfoByFullName(genContext.thisClassType.getName().getValue())) + { + auto accessorIndex = classInfo->getAccessorIndex(name); + if (accessorIndex >= 0) + { + auto &getFunc = classInfo->accessors[accessorIndex].get; + if (getFunc) + { + return {mlir::success(), getFunc.funcType.getNumResults() > 0 + ? getFunc.funcType.getResult(0) + : mlir::Type()}; + } + } + } + } + + return {mlir::failure(), mlir::Type()}; + } + std::tuple MLIRGenImpl::getNameOfFunction(SignatureDeclarationBase signatureDeclarationBaseAST, const GenContext &genContext) { diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 8c48b7011..25f30d97c 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -3276,6 +3276,9 @@ class MLIRGenImpl std::tuple>> mlirGenParameters( SignatureDeclarationBase parametersContextAST, const GenContext &genContext); + std::tuple getPairedGetAccessorType(SignatureDeclarationBase setAccessorAST, + const GenContext &genContext); + std::tuple getNameOfFunction(SignatureDeclarationBase signatureDeclarationBaseAST, const GenContext &genContext); @@ -6781,6 +6784,30 @@ class MLIRGenImpl } }; + // a set accessor takes the value as its last parameter, whose type need not be the one its get + // accessor returns (the accessor's type) + auto setAccessorValueType = [&](mlir::Type setAccessorType, mlir::Type accessorType) -> mlir::Type { + if (!mth.isAnyFunctionType(setAccessorType)) + { + return accessorType; + } + + auto params = mth.getParamsFromFuncRef(setAccessorType); + return params.empty() ? accessorType : params.back(); + }; + + auto setAccessorSymbolValueType = [&](mlir::FlatSymbolRefAttr setAccessor, mlir::Type accessorType) -> mlir::Type { + if (setAccessor) + { + if (auto funcOp = theModule.lookupSymbol(setAccessor)) + { + return setAccessorValueType(funcOp.getFunctionType(), accessorType); + } + } + + return accessorType; + }; + // TODO: logic to support safe cast if (auto safeCastOp = leftExpressionValueBeforeCast.getDefiningOp()) { @@ -6898,7 +6925,7 @@ class MLIRGenImpl } else if (auto accessorOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(accessorOp.getType(0)); + syncSavingValue(setAccessorSymbolValueType(accessorOp.getSetAccessorAttr(), accessorOp.getType(0))); if (!savingValue) { return mlir::failure(); @@ -6913,7 +6940,7 @@ class MLIRGenImpl } else if (auto thisAccessorOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(thisAccessorOp.getType(0)); + syncSavingValue(setAccessorSymbolValueType(thisAccessorOp.getSetAccessorAttr(), thisAccessorOp.getType(0))); if (!savingValue) { return mlir::failure(); @@ -6928,7 +6955,7 @@ class MLIRGenImpl } else if (auto thisAccessorIndirectOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(thisAccessorIndirectOp.getType(0)); + syncSavingValue(setAccessorValueType(thisAccessorIndirectOp.getSetAccessor().getType(), thisAccessorIndirectOp.getType(0))); if (!savingValue) { return mlir::failure(); @@ -6945,7 +6972,7 @@ class MLIRGenImpl } else if (auto thisIndexAccessorOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(thisIndexAccessorOp.getType(0)); + syncSavingValue(setAccessorSymbolValueType(thisIndexAccessorOp.getSetAccessorAttr(), thisIndexAccessorOp.getType(0))); if (!savingValue) { return mlir::failure(); @@ -6960,7 +6987,7 @@ class MLIRGenImpl } else if (auto thisIndirectIndexAccessorOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(thisIndirectIndexAccessorOp.getType(0)); + syncSavingValue(setAccessorValueType(thisIndirectIndexAccessorOp.getSetAccessor().getType(), thisIndirectIndexAccessorOp.getType(0))); if (!savingValue) { return mlir::failure(); @@ -6975,7 +7002,7 @@ class MLIRGenImpl } else if (auto boundAccessorIndirectOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(boundAccessorIndirectOp.getType(0)); + syncSavingValue(setAccessorValueType(boundAccessorIndirectOp.getSetAccessor().getType(), boundAccessorIndirectOp.getType(0))); if (!savingValue) { return mlir::failure(); @@ -6991,7 +7018,7 @@ class MLIRGenImpl } else if (auto boundIndirectIndexAccessorOp = leftExpressionValueBeforeCast.getDefiningOp()) { - syncSavingValue(boundIndirectIndexAccessorOp.getType(0)); + syncSavingValue(setAccessorValueType(boundIndirectIndexAccessorOp.getSetAccessor().getType(), boundIndirectIndexAccessorOp.getType(0))); if (!savingValue) { return mlir::failure(); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 58dd6529e..895c8cd9d 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -609,6 +609,7 @@ tslang_add_test(NAME test-compile-00-class-accessor-super COMMAND test-runner "$ 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-accessor-implements-field COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_implements_field.ts") tslang_add_test(NAME test-compile-00-object-literal-contextual-lambda COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_literal_contextual_lambda.ts") +tslang_add_test(NAME test-compile-00-class-accessor-untyped-setter COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_untyped_setter.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") @@ -1224,6 +1225,7 @@ tslang_add_test(NAME test-jit-00-class-accessor-super COMMAND test-runner -jit " 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-accessor-implements-field COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_implements_field.ts") tslang_add_test(NAME test-jit-00-object-literal-contextual-lambda COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_literal_contextual_lambda.ts") +tslang_add_test(NAME test-jit-00-class-accessor-untyped-setter COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_accessor_untyped_setter.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") @@ -2178,6 +2180,7 @@ set(TSLANG_CORPUS 00class_accessor_virtual.ts 00class_accessor_implements_field.ts 00object_literal_contextual_lambda.ts + 00class_accessor_untyped_setter.ts 00class_accessor.ts 00class_accessor2.ts 00class_constr_fields.ts diff --git a/tslang/test/tester/tests/00class_accessor_untyped_setter.ts b/tslang/test/tester/tests/00class_accessor_untyped_setter.ts new file mode 100644 index 000000000..d5c67a88c --- /dev/null +++ b/tslang/test/tester/tests/00class_accessor_untyped_setter.ts @@ -0,0 +1,57 @@ +// a set accessor's parameter without a type takes the type its get accessor returns +class GetFirst { + f = 1; + get prop() { return this.f; } + set prop(v) { print("set", v); this.f = v; } +} + +// the get accessor after the set accessor +class SetFirst { + f = 1; + set prop(v) { this.f = v + 1; } + get prop() { return this.f; } +} + +// a get accessor with a written type +class Typed { + s = ""; + get prop(): string { return this.s; } + set prop(v) { this.s = v + "!"; } +} + +class Static { + static f = 1; + static get prop() { return Static.f; } + static set prop(v) { Static.f = v * 2; } +} + +// the value is given to a set accessor as the type it takes, not as the one its get accessor returns +class AnySetter { + f = 1; + get prop() { return this.f; } + set prop(v: any) { this.f = v; } +} + +function main() { + const c = new GetFirst(); + c.prop = 5; + print(c.prop); + assert(c.prop == 5); + + const s = new SetFirst(); + s.prop = 5; + assert(s.prop == 6); + + const t = new Typed(); + t.prop = "hi"; + assert(t.prop == "hi!"); + + Static.prop = 4; + assert(Static.prop == 8); + + const a = new AnySetter(); + a.prop = 7; + assert(a.prop == 7); + + print("done."); +}