diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt index 8e34a1739..f7b0fcbbc 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt @@ -16,6 +16,8 @@ package org.jacodb.ets.dto +import java.math.BigInteger + import mu.KotlinLogging import org.jacodb.ets.model.EtsDefineDataPropertyStmt import org.jacodb.ets.model.EtsPropertyRef @@ -38,6 +40,8 @@ import org.jacodb.ets.model.EtsBitOrExpr import org.jacodb.ets.model.EtsBitXorExpr import org.jacodb.ets.model.EtsBlockCfg import org.jacodb.ets.model.EtsBooleanConstant +import org.jacodb.ets.model.EtsBigIntConstant +import org.jacodb.ets.model.EtsBigIntType import org.jacodb.ets.model.EtsBooleanLiteralType import org.jacodb.ets.model.EtsBooleanType import org.jacodb.ets.model.EtsCallExpr @@ -693,6 +697,8 @@ fun TypeDto.toEtsType(): EtsType = when (this) { BooleanTypeDto -> EtsBooleanType + BigIntTypeDto -> EtsBigIntType + is ClassTypeDto -> toEtsClassType() is ClassValueTypeDto -> EtsClassValueType(signature.toEtsClassSignature()) @@ -776,6 +782,8 @@ fun ConstantDto.toEtsConstant(): EtsConstant { EtsNumberType -> EtsNumberConstant(value = value.toDouble()) + EtsBigIntType -> EtsBigIntConstant(value = BigInteger(value)) + EtsNullType -> EtsNullConstant EtsUndefinedType -> EtsUndefinedConstant diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt index d4172c5df..2e991eaa1 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Serializers.kt @@ -35,6 +35,8 @@ import kotlinx.serialization.json.doubleOrNull import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass private val logger = KotlinLogging.logger {} @@ -47,6 +49,9 @@ internal val valueModule = SerializersModule { } internal val typeModule = SerializersModule { + polymorphic(TypeDto::class) { + subclass(BigIntTypeDto::class, BigIntTypeDto.serializer()) + } polymorphicDefaultDeserializer(TypeDto::class) { RawTypeSerializer } } diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt index 623f02063..b5f0bf446 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt @@ -114,6 +114,13 @@ data object NumberTypeDto : PrimitiveTypeDto { get() = "number" } +@Serializable +@SerialName("BigIntType") +data object BigIntTypeDto : PrimitiveTypeDto { + override val name: String + get() = "bigint" +} + @Serializable @SerialName("StringType") data object StringTypeDto : PrimitiveTypeDto { diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Constant.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Constant.kt index c9db2733b..d7ba0e9ae 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Constant.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Constant.kt @@ -16,6 +16,8 @@ package org.jacodb.ets.model +import java.math.BigInteger + interface EtsConstant : EtsImmediate data class EtsStringConstant( @@ -68,6 +70,19 @@ data class EtsNumberConstant( } } +data class EtsBigIntConstant( + val value: BigInteger, +) : EtsConstant { + override val type: EtsType + get() = EtsBigIntType + + override fun toString(): String = "${value}n" + + override fun accept(visitor: EtsValue.Visitor): R { + return visitor.visit(this) + } +} + object EtsNullConstant : EtsConstant { override val type: EtsType get() = EtsNullType diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt index 40314a7f7..8da58d871 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt @@ -36,6 +36,10 @@ interface EtsType : TypeName, CommonType { // Primitive fun visit(type: EtsBooleanType): R fun visit(type: EtsNumberType): R + fun visit(type: EtsBigIntType): R { + if (this is Default) return defaultVisit(type) + error("Cannot handle ${type::class.java.simpleName}: $type") + } fun visit(type: EtsStringType): R fun visit(type: EtsNullType): R fun visit(type: EtsUndefinedType): R @@ -77,6 +81,7 @@ interface EtsType : TypeName, CommonType { override fun visit(type: EtsBooleanType): R = defaultVisit(type) override fun visit(type: EtsNumberType): R = defaultVisit(type) + override fun visit(type: EtsBigIntType): R = defaultVisit(type) override fun visit(type: EtsStringType): R = defaultVisit(type) override fun visit(type: EtsNullType): R = defaultVisit(type) override fun visit(type: EtsUndefinedType): R = defaultVisit(type) @@ -234,6 +239,17 @@ object EtsNumberType : EtsPrimitiveType { } } +object EtsBigIntType : EtsPrimitiveType { + override val typeName: String + get() = "bigint" + + override fun toString(): String = typeName + + override fun accept(visitor: EtsType.Visitor): R { + return visitor.visit(this) + } +} + object EtsStringType : EtsPrimitiveType { override val typeName: String get() = "string" diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Value.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Value.kt index a1e518822..4dd080451 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Value.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Value.kt @@ -31,6 +31,10 @@ interface EtsValue : EtsEntity, CommonValue { fun visit(value: EtsStringConstant): R fun visit(value: EtsBooleanConstant): R fun visit(value: EtsNumberConstant): R + fun visit(value: EtsBigIntConstant): R { + if (this is Default) return defaultVisit(value) + error("Cannot handle ${value::class.java.simpleName}: $value") + } fun visit(value: EtsNullConstant): R fun visit(value: EtsUndefinedConstant): R @@ -57,6 +61,7 @@ interface EtsValue : EtsEntity, CommonValue { override fun visit(value: EtsStringConstant): R = defaultVisit(value) override fun visit(value: EtsBooleanConstant): R = defaultVisit(value) override fun visit(value: EtsNumberConstant): R = defaultVisit(value) + override fun visit(value: EtsBigIntConstant): R = defaultVisit(value) override fun visit(value: EtsNullConstant): R = defaultVisit(value) override fun visit(value: EtsUndefinedConstant): R = defaultVisit(value) diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/GetOperands.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/GetOperands.kt index f405ac279..a5bd6e398 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/GetOperands.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/GetOperands.kt @@ -66,6 +66,7 @@ import org.jacodb.ets.model.EtsNotExpr import org.jacodb.ets.model.EtsNullConstant import org.jacodb.ets.model.EtsNullishCoalescingExpr import org.jacodb.ets.model.EtsNumberConstant +import org.jacodb.ets.model.EtsBigIntConstant import org.jacodb.ets.model.EtsOrExpr import org.jacodb.ets.model.EtsParameterRef import org.jacodb.ets.model.EtsPostDecExpr @@ -157,6 +158,9 @@ private object EntityGetOperands : EtsEntity.Visitor> { override fun visit(value: EtsNumberConstant): Sequence = emptySequence() + override fun visit(value: EtsBigIntConstant): Sequence = + emptySequence() + override fun visit(value: EtsNullConstant): Sequence = emptySequence() diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Handler.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Handler.kt index ad64890b7..36b7d8d89 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Handler.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Handler.kt @@ -66,6 +66,7 @@ import org.jacodb.ets.model.EtsNotExpr import org.jacodb.ets.model.EtsNullConstant import org.jacodb.ets.model.EtsNullishCoalescingExpr import org.jacodb.ets.model.EtsNumberConstant +import org.jacodb.ets.model.EtsBigIntConstant import org.jacodb.ets.model.EtsOrExpr import org.jacodb.ets.model.EtsParameterRef import org.jacodb.ets.model.EtsPostDecExpr @@ -201,6 +202,10 @@ abstract class AbstractHandler : EtsEntity.Visitor, EtsStmt.Visitor handle(value) } + final override fun visit(value: EtsBigIntConstant) { + handle(value) + } + final override fun visit(value: EtsNullConstant) { handle(value) } diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsTsBigIntTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsTsBigIntTest.kt new file mode 100644 index 000000000..f3e575cd8 --- /dev/null +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsTsBigIntTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * 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 + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 java.math.BigInteger +import org.jacodb.ets.dto.AssignStmtDto +import org.jacodb.ets.dto.BigIntTypeDto +import org.jacodb.ets.dto.ConstantDto +import org.jacodb.ets.model.EtsAssignStmt +import org.jacodb.ets.model.EtsBigIntConstant +import org.jacodb.ets.model.EtsBigIntType + +class EtsTsBigIntTest { + 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 `bigint types and exact constants survive frontend JSON and model conversion`() { + val frontendDto = runFrontend( + source = + """ + export function increment(x: bigint): bigint { return x + 1n; } + export const large = 9007199254740993n; + export const negative = -0x20n; + """.trimIndent(), + ) + val roundTripped = EtsFileDto.loadFromJson( + Json { serializersModule = dtoModule }.encodeToString(frontendDto), + ) + val defaultClass = roundTripped.classes.single { it.signature.name == DEFAULT_ARK_CLASS_NAME } + val incrementDto = defaultClass.methods.single { it.signature.name == "increment" } + val constantsDto = defaultClass.methods.flatMap { it.body?.cfg?.blocks.orEmpty() } + .flatMap { it.stmts } + .filterIsInstance() + .mapNotNull { it.right as? ConstantDto } + .filter { it.type == BigIntTypeDto } + + assertEquals(BigIntTypeDto, incrementDto.signature.parameters.single().type) + assertEquals(BigIntTypeDto, incrementDto.signature.returnType) + assertTrue(constantsDto.any { it.value == "9007199254740993" }) + assertTrue(constantsDto.any { it.value == "-32" }) + + val modelClass = roundTripped.toEtsFile().classes.single { it.name == DEFAULT_ARK_CLASS_NAME } + val incrementModel = modelClass.methods.single { it.name == "increment" } + val bigInts = modelClass.methods.flatMap { it.cfg.stmts } + .filterIsInstance() + .mapNotNull { it.rhv as? EtsBigIntConstant } + + assertEquals(EtsBigIntType, incrementModel.signature.parameters.single().type) + assertEquals(EtsBigIntType, incrementModel.signature.returnType) + assertTrue(bigInts.any { it.value == BigInteger("9007199254740993") }) + assertTrue(bigInts.any { it.value == BigInteger("-32") }) + } + +} diff --git a/jacodb-ets/ts-frontend/src/dto/types.ts b/jacodb-ets/ts-frontend/src/dto/types.ts index e99c7ae17..89c37191e 100644 --- a/jacodb-ets/ts-frontend/src/dto/types.ts +++ b/jacodb-ets/ts-frontend/src/dto/types.ts @@ -40,6 +40,7 @@ export type TypeDto = | IntersectionTypeDto | BooleanTypeDto | NumberTypeDto + | BigIntTypeDto | StringTypeDto | NullTypeDto | UndefinedTypeDto @@ -118,6 +119,10 @@ export interface NumberTypeDto { readonly _: "NumberType"; } +export interface BigIntTypeDto { + readonly _: "BigIntType"; +} + export interface StringTypeDto { readonly _: "StringType"; } @@ -178,6 +183,7 @@ export const VOID_TYPE: VoidTypeDto = { _: "VoidType" }; export const NEVER_TYPE: NeverTypeDto = { _: "NeverType" }; export const BOOLEAN_TYPE: BooleanTypeDto = { _: "BooleanType" }; export const NUMBER_TYPE: NumberTypeDto = { _: "NumberType" }; +export const BIGINT_TYPE: BigIntTypeDto = { _: "BigIntType" }; export const STRING_TYPE: StringTypeDto = { _: "StringType" }; export const NULL_TYPE: NullTypeDto = { _: "NullType" }; export const UNDEFINED_TYPE: UndefinedTypeDto = { _: "UndefinedType" }; diff --git a/jacodb-ets/ts-frontend/src/lowering/exprLowering.ts b/jacodb-ets/ts-frontend/src/lowering/exprLowering.ts index 42c774d71..2e3b7b17f 100644 --- a/jacodb-ets/ts-frontend/src/lowering/exprLowering.ts +++ b/jacodb-ets/ts-frontend/src/lowering/exprLowering.ts @@ -49,6 +49,7 @@ import { } from "../dto/signatures"; import { BOOLEAN_TYPE, + BIGINT_TYPE, ClassTypeDto, NUMBER_TYPE, NULL_TYPE, @@ -211,6 +212,9 @@ export class ExprLowerer { if (ts.isNumericLiteral(node)) { return constant(numericConstantText(node), NUMBER_TYPE); } + if (ts.isBigIntLiteral(node)) { + return constant(bigIntConstantText(node), BIGINT_TYPE); + } if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) { return constant(node.text, STRING_TYPE); } @@ -903,6 +907,9 @@ export class ExprLowerer { if (ts.isNumericLiteral(node.operand)) { return constant(`-${numericConstantText(node.operand)}`, NUMBER_TYPE); } + if (ts.isBigIntLiteral(node.operand)) { + return constant(`-${bigIntConstantText(node.operand)}`, BIGINT_TYPE); + } const operand = this.lowerToImmediate(node.operand); if (operand._ === "Constant" && operand.value === "Infinity" && operand.type._ === "NumberType") { return constant("-Infinity", NUMBER_TYPE); @@ -1949,6 +1956,10 @@ function numericConstantText(node: ts.NumericLiteral): string { return Number.isFinite(Number(node.text)) ? node.text : node.getText(); } +function bigIntConstantText(node: ts.BigIntLiteral): string { + return BigInt(node.text.replace(/_/g, "").replace(/n$/, "")).toString(); +} + function objectField(declaringClass: ClassSignatureDto, name: string, type: TypeDto): FieldDto { return { signature: { declaringClass, name, type }, diff --git a/jacodb-ets/ts-frontend/src/types/convert.ts b/jacodb-ets/ts-frontend/src/types/convert.ts index ccd4a6d03..087f4f876 100644 --- a/jacodb-ets/ts-frontend/src/types/convert.ts +++ b/jacodb-ets/ts-frontend/src/types/convert.ts @@ -43,6 +43,7 @@ import { ANY_TYPE, ArrayTypeDto, BOOLEAN_TYPE, + BIGINT_TYPE, ClassTypeDto, GenericTypeDto, NEVER_TYPE, @@ -163,8 +164,9 @@ export class TypeConverter { case ts.SyntaxKind.BooleanKeyword: return BOOLEAN_TYPE; case ts.SyntaxKind.NumberKeyword: - case ts.SyntaxKind.BigIntKeyword: return NUMBER_TYPE; + case ts.SyntaxKind.BigIntKeyword: + return BIGINT_TYPE; case ts.SyntaxKind.StringKeyword: return STRING_TYPE; case ts.SyntaxKind.ObjectKeyword: @@ -371,12 +373,19 @@ export class TypeConverter { if (ts.isNumericLiteral(literal)) { return numericLiteralType(Number(literal.text)); } + if (ts.isBigIntLiteral(literal)) { + return BIGINT_TYPE; + } if ( ts.isPrefixUnaryExpression(literal) && - literal.operator === ts.SyntaxKind.MinusToken && - ts.isNumericLiteral(literal.operand) + literal.operator === ts.SyntaxKind.MinusToken ) { - return numericLiteralType(-Number(literal.operand.text)); + if (ts.isNumericLiteral(literal.operand)) { + return numericLiteralType(-Number(literal.operand.text)); + } + if (ts.isBigIntLiteral(literal.operand)) { + return BIGINT_TYPE; + } } return UNKNOWN_TYPE; } @@ -558,7 +567,7 @@ export class TypeConverter { if (flags & ts.TypeFlags.BooleanLike) return BOOLEAN_TYPE; if (flags & ts.TypeFlags.NumberLike) return NUMBER_TYPE; - if (flags & ts.TypeFlags.BigIntLike) return NUMBER_TYPE; + if (flags & ts.TypeFlags.BigIntLike) return BIGINT_TYPE; if (flags & ts.TypeFlags.StringLike) return STRING_TYPE; if (flags & ts.TypeFlags.TypeParameter) { diff --git a/jacodb-ets/ts-frontend/test/lowering.spec.ts b/jacodb-ets/ts-frontend/test/lowering.spec.ts index f8674b36f..a08e9111e 100644 --- a/jacodb-ets/ts-frontend/test/lowering.spec.ts +++ b/jacodb-ets/ts-frontend/test/lowering.spec.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import * as ts from "typescript"; import { StmtDto, AssignStmtDto, CallStmtDto } from "../src/dto/stmts"; import { serializeEtsFile } from "../src/serialize"; import { defaultMethod, lower, methodByName, singleBlockStmts } from "./util"; @@ -25,6 +26,44 @@ function assignmentTo(stmts: StmtDto[], name: string): AssignStmtDto | undefined } describe("straight-line lowering", () => { + it("preserves bigint operations and exact literals through JSON", () => { + const source = ` + export function increment(x: bigint): bigint { return x + 1n; } + export const large = 9007199254740993n; + export const negative = -0x20n; + `; + const { file, diagnostics } = lower(source); + const roundTripped = JSON.parse(serializeEtsFile(file)); + const increment = methodByName(roundTripped, "increment"); + const defaultStatements = singleBlockStmts(defaultMethod(roundTripped)); + const incrementStatements = singleBlockStmts(increment); + + expect(diagnostics.messages).toEqual([]); + expect(increment.signature.parameters[0].type).toEqual({ _: "BigIntType" }); + expect(increment.signature.returnType).toEqual({ _: "BigIntType" }); + expect(incrementStatements).toContainEqual(expect.objectContaining({ + _: "AssignStmt", + right: expect.objectContaining({ + _: "BinopExpr", + op: "+", + right: { _: "Constant", value: "1", type: { _: "BigIntType" } }, + type: { _: "BigIntType" }, + }), + })); + expect(assignmentTo(defaultStatements, "large")?.right).toEqual({ + _: "Constant", value: "9007199254740993", type: { _: "BigIntType" }, + }); + expect(assignmentTo(defaultStatements, "negative")?.right).toEqual({ + _: "Constant", value: "-32", type: { _: "BigIntType" }, + }); + + const javascript = ts.transpileModule(source, { + compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS }, + }).outputText; + const concrete = new Function("exports", `${javascript}\nreturn [exports.increment(1n), exports.large, exports.negative];`)({}); + expect(concrete).toEqual([2n, 9007199254740993n, -32n]); + }); + it("lowers literal initializers to constants", () => { const stmts = bodyStmts(`let x = 42; let s = "hi"; let b = true; let n = null; let u = undefined;`); expect(stmts).toEqual([ diff --git a/jacodb-ets/ts-frontend/test/types.spec.ts b/jacodb-ets/ts-frontend/test/types.spec.ts index 0218d548f..d56eb6b32 100644 --- a/jacodb-ets/ts-frontend/test/types.spec.ts +++ b/jacodb-ets/ts-frontend/test/types.spec.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest"; import { ClassCategory } from "../src/dto/constants"; import { FileSignatureDto } from "../src/dto/signatures"; import { TypeDto } from "../src/dto/types"; +import { serializeEtsFile } from "../src/serialize"; import { TypeConverter } from "../src/types/convert"; import { compile, findNode, findVariable, lower, methodByName } from "./util"; @@ -37,7 +38,7 @@ describe("convertTypeNode (annotations)", () => { expect(annotationOf("let x: unknown;")).toEqual({ _: "UnknownType" }); expect(annotationOf("let x: undefined;")).toEqual({ _: "UndefinedType" }); expect(annotationOf("let x: never;")).toEqual({ _: "NeverType" }); - expect(annotationOf("let x: bigint;")).toEqual({ _: "NumberType" }); + expect(annotationOf("let x: bigint;")).toEqual({ _: "BigIntType" }); }); it("converts literal types with bare JSON primitives", () => { @@ -48,6 +49,29 @@ describe("convertTypeNode (annotations)", () => { expect(annotationOf("let x: null;")).toEqual({ _: "NullType" }); }); + it("widens bigint literal annotations in parameters, locals, and returns", () => { + const { file, diagnostics } = lower(` + function positive(value: 1n): 1n { + const local: 1n = value; + return local; + } + function negative(value: -1n): -1n { + const local: -1n = value; + return local; + } + `); + const roundTripped = JSON.parse(serializeEtsFile(file)); + + expect(diagnostics.messages).toEqual([]); + for (const name of ["positive", "negative"]) { + const method = methodByName(roundTripped, name); + + expect(method.signature.parameters[0].type).toEqual({ _: "BigIntType" }); + expect(method.signature.returnType).toEqual({ _: "BigIntType" }); + expect(method.body!.locals.find((local) => local.name === "local")?.type).toEqual({ _: "BigIntType" }); + } + }); + it("converts arrays and folds dimensions", () => { expect(annotationOf("let x: number[];")).toEqual({ _: "ArrayType", @@ -317,6 +341,7 @@ describe("typeOfNode (inference)", () => { expect(inferredOf("let x = 42;")).toEqual({ _: "NumberType" }); expect(inferredOf(`let x = "s";`)).toEqual({ _: "StringType" }); expect(inferredOf("let x = true;")).toEqual({ _: "BooleanType" }); + expect(inferredOf("let x = 1n;")).toEqual({ _: "BigIntType" }); }); it("keeps literal types for const-declarations", () => {