Skip to content

Commit 847a070

Browse files
Enhance method member handling to preserve 'this' reference in type-literal annotations (#258)
1 parent 4be261b commit 847a070

5 files changed

Lines changed: 88 additions & 2 deletions

File tree

tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ class CastLogicHelper
216216

217217
if (auto resFuncType = dyn_cast<mlir_ts::FunctionType>(resType))
218218
{
219+
// dropping a bound/hybrid value to a plain FunctionType only loses the
220+
// receiver when the target has no `this` slot of its own. A target
221+
// whose first input is opaque/object-typed (method-member convention,
222+
// see isBoundReference) keeps the this param in the funcptr signature
223+
// and the receiver is re-bound from the base object at every property
224+
// access - nothing is lost, so no warning.
225+
auto resKeepsThisParam = resFuncType.getNumInputs() > 0 &&
226+
(isa<mlir_ts::OpaqueType>(resFuncType.getInput(0)) ||
227+
isa<mlir_ts::ObjectType>(resFuncType.getInput(0)));
228+
219229
if (auto inBoundFunc = dyn_cast<mlir_ts::BoundFunctionType>(inType))
220230
{
221231
// somehow llvm.trampoline accepts only direct method symbol
@@ -224,7 +234,10 @@ class CastLogicHelper
224234
auto methodVal = rewriter.create<mlir_ts::GetMethodOp>(loc, resFuncType, in);
225235
return rewriter.create<mlir_ts::TrampolineOp>(loc, resFuncType, methodVal, thisVal);
226236
*/
227-
op->emitWarning("losing this reference");
237+
if (!resKeepsThisParam)
238+
{
239+
op->emitWarning("losing this reference");
240+
}
228241
/*
229242
// you can wrap into () => {} lambda call to capture vars
230243
const user = {
@@ -249,7 +262,10 @@ class CastLogicHelper
249262
// and re-supplied by the caller (e.g. an interface vtable call passes its own
250263
// thisVal) - this is the vtable-method-pointer path for a cross-module tuple
251264
// value cast to a method-bearing interface.
252-
op->emitWarning("losing this reference");
265+
if (!resKeepsThisParam)
266+
{
267+
op->emitWarning("losing this reference");
268+
}
253269
return rewriter.create<mlir_ts::GetMethodOp>(loc, resFuncType, in);
254270
}
255271
}

tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ class MLIRPrinter
3333
auto isVar = t.getIsVarArg();
3434
for (auto subType : t.getInputs())
3535
{
36+
// an opaque/object-typed first input is an implicit `this` param
37+
// (method-member convention, see isBoundReference) - it is not
38+
// declarable in source syntax ("Opaque" doesn't parse back), so
39+
// omit it like DeclarationPrinter::printParams omits `this`.
40+
if (index == 0 &&
41+
(isa<mlir_ts::OpaqueType>(subType) || isa<mlir_ts::ObjectType>(subType)))
42+
{
43+
index++;
44+
size--;
45+
continue;
46+
}
47+
3648
if (!first)
3749
{
3850
out << ", ";

tslang/lib/TypeScript/MLIRGenTypes.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,6 +2603,26 @@ namespace mlirgen
26032603
return mlir::failure();
26042604
}
26052605

2606+
// a method member is called through the object, so its field type
2607+
// must declare an implicit `this` first param (opaque, same
2608+
// convention as interface method funcTypes): that is what makes
2609+
// property access create a bound reference (isBoundReference)
2610+
// binding the receiver. A bare this-less FunctionType here made
2611+
// the annotation cast drop `this` ("losing this reference") and
2612+
// later calls pass a garbage receiver.
2613+
if (auto funcType = dyn_cast<mlir_ts::FunctionType>(type))
2614+
{
2615+
if (funcType.getNumInputs() == 0 ||
2616+
!(isa<mlir_ts::OpaqueType>(funcType.getInput(0)) || isa<mlir_ts::ObjectType>(funcType.getInput(0))))
2617+
{
2618+
SmallVector<mlir::Type> inputs;
2619+
inputs.push_back(mlir_ts::OpaqueType::get(builder.getContext()));
2620+
inputs.append(funcType.getInputs().begin(), funcType.getInputs().end());
2621+
type = mlir_ts::FunctionType::get(builder.getContext(), inputs, funcType.getResults(),
2622+
funcType.isVarArg());
2623+
}
2624+
}
2625+
26062626
types.push_back({TupleFieldName(methodSignature->name, genContext), type, false, mlir_ts::AccessLevel::Public});
26072627
}
26082628
else if (kind == SyntaxKind::ConstructSignature)

tslang/test/tester/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ add_test(NAME test-compile-00-interface-optional COMMAND test-runner "${PROJECT_
287287
add_test(NAME test-compile-00-interface-optional-cast-order COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_optional_cast_order.ts")
288288
add_test(NAME test-compile-00-interface-function-typed-field COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_function_typed_field.ts")
289289
add_test(NAME test-compile-00-interface-captures COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts")
290+
add_test(NAME test-compile-00-object-annotated-method COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts")
290291
add_test(NAME test-compile-00-interface-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts")
291292
add_test(NAME test-compile-00-interface-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts")
292293
add_test(NAME test-compile-00-interface-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts")
@@ -640,6 +641,7 @@ add_test(NAME test-jit-00-interface-optional COMMAND test-runner -jit "${PROJECT
640641
add_test(NAME test-jit-00-interface-optional-cast-order COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_optional_cast_order.ts")
641642
add_test(NAME test-jit-00-interface-function-typed-field COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_function_typed_field.ts")
642643
add_test(NAME test-jit-00-interface-captures COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts")
644+
add_test(NAME test-jit-00-object-annotated-method COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts")
643645
add_test(NAME test-jit-00-interface-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts")
644646
add_test(NAME test-jit-00-interface-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts")
645647
add_test(NAME test-jit-00-interface-indexer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// A type-literal annotation with a METHOD member (`inc(): void`, MethodSignature)
2+
// must keep `this` callable through the annotated value: the member's field type
3+
// carries an implicit opaque `this` param (same convention as interface method
4+
// funcTypes), so property access binds the receiver. Previously the member
5+
// degraded to a this-less funcptr: the annotation cast dropped the receiver
6+
// ("losing this reference") and calls silently passed garbage as `this` -
7+
// mutations went nowhere (count stayed 0).
8+
9+
var counterObj: { count: number; inc(): void } = { count: 0, inc() { this.count = this.count + 1; } };
10+
11+
function localAnnotated() {
12+
let c: { count: number; inc(): void } = { count: 10, inc() { this.count = this.count + 1; } };
13+
c.inc();
14+
c.inc();
15+
assert(c.count == 12);
16+
print(c.count);
17+
}
18+
19+
function reader() {
20+
let obj: { base: number; twice(): number } = { base: 21, twice() { return this.base * 2; } };
21+
const v = obj.twice();
22+
assert(v == 42);
23+
print(v);
24+
}
25+
26+
function main() {
27+
counterObj.inc();
28+
counterObj.inc();
29+
print(counterObj.count);
30+
assert(counterObj.count == 2);
31+
32+
localAnnotated();
33+
reader();
34+
35+
print("done.");
36+
}

0 commit comments

Comments
 (0)