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
20 changes: 18 additions & 2 deletions tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ class CastLogicHelper

if (auto resFuncType = dyn_cast<mlir_ts::FunctionType>(resType))
{
// dropping a bound/hybrid value to a plain FunctionType only loses the
// receiver when the target has no `this` slot of its own. A target
// whose first input is opaque/object-typed (method-member convention,
// see isBoundReference) keeps the this param in the funcptr signature
// and the receiver is re-bound from the base object at every property
// access - nothing is lost, so no warning.
auto resKeepsThisParam = resFuncType.getNumInputs() > 0 &&
(isa<mlir_ts::OpaqueType>(resFuncType.getInput(0)) ||
isa<mlir_ts::ObjectType>(resFuncType.getInput(0)));

if (auto inBoundFunc = dyn_cast<mlir_ts::BoundFunctionType>(inType))
{
// somehow llvm.trampoline accepts only direct method symbol
Expand All @@ -224,7 +234,10 @@ class CastLogicHelper
auto methodVal = rewriter.create<mlir_ts::GetMethodOp>(loc, resFuncType, in);
return rewriter.create<mlir_ts::TrampolineOp>(loc, resFuncType, methodVal, thisVal);
*/
op->emitWarning("losing this reference");
if (!resKeepsThisParam)
{
op->emitWarning("losing this reference");
}
/*
// you can wrap into () => {} lambda call to capture vars
const user = {
Expand All @@ -249,7 +262,10 @@ class CastLogicHelper
// and re-supplied by the caller (e.g. an interface vtable call passes its own
// thisVal) - this is the vtable-method-pointer path for a cross-module tuple
// value cast to a method-bearing interface.
op->emitWarning("losing this reference");
if (!resKeepsThisParam)
{
op->emitWarning("losing this reference");
}
return rewriter.create<mlir_ts::GetMethodOp>(loc, resFuncType, in);
}
}
Expand Down
12 changes: 12 additions & 0 deletions tslang/include/TypeScript/MLIRLogic/MLIRPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ class MLIRPrinter
auto isVar = t.getIsVarArg();
for (auto subType : t.getInputs())
{
// an opaque/object-typed first input is an implicit `this` param
// (method-member convention, see isBoundReference) - it is not
// declarable in source syntax ("Opaque" doesn't parse back), so
// omit it like DeclarationPrinter::printParams omits `this`.
if (index == 0 &&
(isa<mlir_ts::OpaqueType>(subType) || isa<mlir_ts::ObjectType>(subType)))
{
index++;
size--;
continue;
}

if (!first)
{
out << ", ";
Expand Down
20 changes: 20 additions & 0 deletions tslang/lib/TypeScript/MLIRGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,26 @@ namespace mlirgen
return mlir::failure();
}

// a method member is called through the object, so its field type
// must declare an implicit `this` first param (opaque, same
// convention as interface method funcTypes): that is what makes
// property access create a bound reference (isBoundReference)
// binding the receiver. A bare this-less FunctionType here made
// the annotation cast drop `this` ("losing this reference") and
// later calls pass a garbage receiver.
if (auto funcType = dyn_cast<mlir_ts::FunctionType>(type))
{
if (funcType.getNumInputs() == 0 ||
!(isa<mlir_ts::OpaqueType>(funcType.getInput(0)) || isa<mlir_ts::ObjectType>(funcType.getInput(0))))
{
SmallVector<mlir::Type> inputs;
inputs.push_back(mlir_ts::OpaqueType::get(builder.getContext()));
inputs.append(funcType.getInputs().begin(), funcType.getInputs().end());
type = mlir_ts::FunctionType::get(builder.getContext(), inputs, funcType.getResults(),
funcType.isVarArg());
}
}

types.push_back({TupleFieldName(methodSignature->name, genContext), type, false, mlir_ts::AccessLevel::Public});
}
else if (kind == SyntaxKind::ConstructSignature)
Expand Down
2 changes: 2 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ add_test(NAME test-compile-00-interface-optional COMMAND test-runner "${PROJECT_
add_test(NAME test-compile-00-interface-optional-cast-order COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_optional_cast_order.ts")
add_test(NAME test-compile-00-interface-function-typed-field COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_function_typed_field.ts")
add_test(NAME test-compile-00-interface-captures COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts")
add_test(NAME test-compile-00-object-annotated-method COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts")
add_test(NAME test-compile-00-interface-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts")
add_test(NAME test-compile-00-interface-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts")
add_test(NAME test-compile-00-interface-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts")
Expand Down Expand Up @@ -640,6 +641,7 @@ add_test(NAME test-jit-00-interface-optional COMMAND test-runner -jit "${PROJECT
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")
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")
add_test(NAME test-jit-00-interface-captures COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts")
add_test(NAME test-jit-00-object-annotated-method COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts")
add_test(NAME test-jit-00-interface-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts")
add_test(NAME test-jit-00-interface-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts")
add_test(NAME test-jit-00-interface-indexer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts")
Expand Down
36 changes: 36 additions & 0 deletions tslang/test/tester/tests/00object_annotated_method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// A type-literal annotation with a METHOD member (`inc(): void`, MethodSignature)
// must keep `this` callable through the annotated value: the member's field type
// carries an implicit opaque `this` param (same convention as interface method
// funcTypes), so property access binds the receiver. Previously the member
// degraded to a this-less funcptr: the annotation cast dropped the receiver
// ("losing this reference") and calls silently passed garbage as `this` -
// mutations went nowhere (count stayed 0).

var counterObj: { count: number; inc(): void } = { count: 0, inc() { this.count = this.count + 1; } };

function localAnnotated() {
let c: { count: number; inc(): void } = { count: 10, inc() { this.count = this.count + 1; } };
c.inc();
c.inc();
assert(c.count == 12);
print(c.count);
}

function reader() {
let obj: { base: number; twice(): number } = { base: 21, twice() { return this.base * 2; } };
const v = obj.twice();
assert(v == 42);
print(v);
}

function main() {
counterObj.inc();
counterObj.inc();
print(counterObj.count);
assert(counterObj.count == 2);

localAnnotated();
reader();

print("done.");
}
Loading