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
1 change: 1 addition & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ class EtsMethodBuilder(
is YieldExprDto -> EtsYieldExpr(
arg = arg.toEtsEntity(),
type = type.toEtsType(),
isDelegating = isDelegating,
)

is TypeOfExprDto -> EtsTypeOfExpr(
Expand Down
1 change: 1 addition & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Values.kt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ data class AwaitExprDto(
@SerialName("YieldExpr")
data class YieldExprDto(
val arg: ValueDto,
val isDelegating: Boolean = false,
) : ExprDto {
override val type: TypeDto
get() = arg.type
Expand Down
3 changes: 2 additions & 1 deletion jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Expr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,10 @@ data class EtsAwaitExpr(
data class EtsYieldExpr(
override val arg: EtsEntity,
override val type: EtsType,
val isDelegating: Boolean = false,
) : EtsUnaryExpr {
override fun toString(): String {
return "yield $arg"
return if (isDelegating) "yield* $arg" else "yield $arg"
}

override fun <R> accept(visitor: EtsExpr.Visitor<R>): R {
Expand Down
90 changes: 90 additions & 0 deletions jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsTsYieldTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jacodb.ets.test

import kotlin.io.path.createTempDirectory
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jacodb.ets.dto.EtsFileDto
import org.jacodb.ets.dto.dtoModule
import org.jacodb.ets.dto.toEtsFile
import org.jacodb.ets.utils.DEFAULT_ARK_CLASS_NAME
import org.jacodb.ets.utils.EtsIrProvider
import org.jacodb.ets.utils.generateEtsIR
import org.junit.jupiter.api.Test
import org.jacodb.ets.dto.AssignStmtDto
import org.jacodb.ets.dto.YieldExprDto
import org.jacodb.ets.model.EtsAssignStmt
import org.jacodb.ets.model.EtsYieldExpr

class EtsTsYieldTest {
companion object {
/** Run the PRODUCTION integration path: generateEtsIR + EtsFileDto.loadFromJson. */
private fun runFrontend(source: String, fileName: String = "test.ts"): EtsFileDto {
val dir = createTempDirectory("ts-frontend-test")
val inputPath = dir.resolve(fileName)
inputPath.writeText(source)

val outputPath = generateEtsIR(
inputPath,
isProject = false,
timeout = null,
provider = EtsIrProvider.TS_FRONTEND,
)
assertTrue(outputPath.exists(), "ts-frontend did not produce output: $outputPath")

return EtsFileDto.loadFromJson(outputPath.readText())
}
}

@Test
fun `yield delegation survives frontend JSON and model conversion`() {
val frontendDto = runFrontend(
source = """
export function* values() {
yield* [1, 2];
yield [3];
}
""".trimIndent(),
)
val roundTripped = EtsFileDto.loadFromJson(
Json { serializersModule = dtoModule }.encodeToString(frontendDto),
)
val methodDto = roundTripped.classes.single { it.signature.name == DEFAULT_ARK_CLASS_NAME }
.methods.single { it.signature.name == "values" }
val yieldsDto = methodDto.body!!.cfg.blocks.flatMap { it.stmts }
.filterIsInstance<AssignStmtDto>()
.mapNotNull { it.right as? YieldExprDto }

assertEquals(listOf(true, false), yieldsDto.map { it.isDelegating })

val modelMethod = roundTripped.toEtsFile().classes.single { it.name == DEFAULT_ARK_CLASS_NAME }
.methods.single { it.name == "values" }
val modelYields = modelMethod.cfg.stmts.filterIsInstance<EtsAssignStmt>()
.mapNotNull { it.rhv as? EtsYieldExpr }

assertEquals(listOf(true, false), modelYields.map { it.isDelegating })
assertTrue(modelYields[0].toString().startsWith("yield* "))
assertTrue(modelYields[1].toString().startsWith("yield "))
}

}
1 change: 1 addition & 0 deletions jacodb-ets/ts-frontend/src/dto/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export interface AwaitExprDto {
export interface YieldExprDto {
readonly _: "YieldExpr";
arg: ValueDto;
isDelegating?: boolean; // Kotlin default: false
}

export interface TypeOfExprDto {
Expand Down
1 change: 1 addition & 0 deletions jacodb-ets/ts-frontend/src/lowering/exprLowering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ export class ExprLowerer {
arg: node.expression !== undefined
? this.lowerToImmediate(node.expression)
: constant("undefined", UNDEFINED_TYPE),
isDelegating: node.asteriskToken !== undefined,
};
}
throw new LoweringError(ts.SyntaxKind[node.kind]);
Expand Down
15 changes: 15 additions & 0 deletions jacodb-ets/ts-frontend/test/closures.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { serializeEtsFile } from "../src/serialize";
import { AssignStmtDto, StmtDto } from "../src/dto/stmts";
import { validateEtsFile } from "../src/validate";
import { defaultMethod, lower, methodByName, singleBlockStmts } from "./util";
Expand Down Expand Up @@ -417,4 +418,18 @@ describe("generators", () => {
const yields = stmts.filter((s) => s._ === "AssignStmt" && s.right._ === "YieldExpr");
expect(yields).toHaveLength(2);
});

it("preserves yield-star delegation through JSON", () => {
const source = "function* values() { yield* [1, 2]; yield [3]; }";
const { file, diagnostics } = lower(source);
const roundTripped = JSON.parse(serializeEtsFile(file));
const statements = methodByName(roundTripped, "values").body!.cfg.blocks.flatMap((block) => block.stmts);
const yields = statements.filter((statement) => statement._ === "AssignStmt" && statement.right._ === "YieldExpr");

expect(diagnostics.messages).toEqual([]);
expect(yields.map((statement) => statement.right.isDelegating)).toEqual([true, false]);

const concrete = new Function(`${source}; return [...values()];`)();
expect(concrete).toEqual([1, 2, [3]]);
});
});