diff --git a/build.sbt b/build.sbt index ad1bd75..412fd4f 100644 --- a/build.sbt +++ b/build.sbt @@ -156,8 +156,7 @@ lazy val test = project .dependsOn(runtime, pncs) .settings( libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.8.4" % Test, "org.scalatest" %% "scalatest" % "3.2.19" % Test - ), - testFrameworks += new TestFramework("utest.runner.Framework") + ) ) + diff --git a/test/src/test/scala/ArgsParserTests.scala b/test/src/test/scala/ArgsParserTests.scala index 76fa882..b9bf64f 100644 --- a/test/src/test/scala/ArgsParserTests.scala +++ b/test/src/test/scala/ArgsParserTests.scala @@ -1,5 +1,4 @@ import TestHelpers._ -import utest._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers diff --git a/test/src/test/scala/BinderTests.scala b/test/src/test/scala/BinderTests.scala index 104a140..7121592 100644 --- a/test/src/test/scala/BinderTests.scala +++ b/test/src/test/scala/BinderTests.scala @@ -1,269 +1,263 @@ -import TestHelpers._ -import utest._ +import TestHelpers.* +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers -object BinderTests extends TestSuite { - val tests = Tests { - test("builtin symbols") { - val comp = mkCompilation("") - val symbols = enumSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "any") - assertSymbol(symbols, SymbolKind.Class, "int") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "string") - assertSymbol(symbols, SymbolKind.Field, "length") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "bool") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "char") - assertConversionMethod(symbols) - assertSymbol(symbols, SymbolKind.Class, "unit") - assertSymbol(symbols, SymbolKind.Class, "Array") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "size") - assertSymbol(symbols, SymbolKind.Field, "length") - assertSymbol(symbols, SymbolKind.Method, "apply") - assertSymbol(symbols, SymbolKind.Parameter, "index") - // assertSymbol(symbols, SymbolKind.Object, "predef") - assertSymbol(symbols, SymbolKind.Method, "println") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "print") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "panic") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "assert") - assertSymbol(symbols, SymbolKind.Parameter, "condition") - assertSymbol(symbols, SymbolKind.Parameter, "message") - assertSymbol(symbols, SymbolKind.Method, "mod") - assertSymbol(symbols, SymbolKind.Parameter, "a") - assertSymbol(symbols, SymbolKind.Parameter, "b") - assertSymbol(symbols, SymbolKind.Object, "$Program") - assertSymbol(symbols, SymbolKind.Method, "$runtimeInit") - assertSymbol(symbols, SymbolKind.Method, "main") - assertNoSymbols(symbols) - } +class BinderTests extends AnyFlatSpec with Matchers { - test("top level fields") { - val comp = mkCompilation("val x = 12") - val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "x") - assertSymbol(symbols, SymbolKind.Method, "main") - assertNoSymbols(symbols) - } + "Binder" should "create builtin symbols" in { + val comp = mkCompilation("") + val symbols = enumSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "any") + assertSymbol(symbols, SymbolKind.Class, "int") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "string") + assertSymbol(symbols, SymbolKind.Field, "length") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "bool") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "char") + assertConversionMethod(symbols) + assertSymbol(symbols, SymbolKind.Class, "unit") + assertSymbol(symbols, SymbolKind.Class, "Array") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "size") + assertSymbol(symbols, SymbolKind.Field, "length") + assertSymbol(symbols, SymbolKind.Method, "apply") + assertSymbol(symbols, SymbolKind.Parameter, "index") + // assertSymbol(symbols, SymbolKind.Object, "predef") + assertSymbol(symbols, SymbolKind.Method, "println") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "print") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "panic") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "assert") + assertSymbol(symbols, SymbolKind.Parameter, "condition") + assertSymbol(symbols, SymbolKind.Parameter, "message") + assertSymbol(symbols, SymbolKind.Method, "mod") + assertSymbol(symbols, SymbolKind.Parameter, "a") + assertSymbol(symbols, SymbolKind.Parameter, "b") + assertSymbol(symbols, SymbolKind.Object, "$Program") + assertSymbol(symbols, SymbolKind.Method, "$runtimeInit") + assertSymbol(symbols, SymbolKind.Method, "main") + assertNoSymbols(symbols) + } - test("methods") { - val comp = mkCompilation("def foo() = 12") - val symbols = enumNonBuiltinSymbols(comp) + it should "bind top level fields" in { + val comp = mkCompilation("val x = 12") + val symbols = enumNonBuiltinSymbols(comp) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "x") + assertSymbol(symbols, SymbolKind.Method, "main") + assertNoSymbols(symbols) + } - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Method, "foo") - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + it should "bind methods" in { + val comp = mkCompilation("def foo() = 12") + val symbols = enumNonBuiltinSymbols(comp) - test("classes") { - test("without args") { - val comp = mkCompilation("class Foo()") - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "Foo") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertProgramSymbol(symbols) - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Method, "foo") + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } - test("with args") { - val comp = mkCompilation("class Foo(x: int, y: int)") - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "Foo") - assertSymbol(symbols, SymbolKind.Field, "y") - assertSymbol(symbols, SymbolKind.Field, "x") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "x") - assertSymbol(symbols, SymbolKind.Parameter, "y") + it should "bind classes without args" in { + val comp = mkCompilation("class Foo()") + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "Foo") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertProgramSymbol(symbols) + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } - assertProgramSymbol(symbols) - assertMainSymbol(symbols) + it should "bind classes with args" in { + val comp = mkCompilation("class Foo(x: int, y: int)") + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "Foo") + assertSymbol(symbols, SymbolKind.Field, "y") + assertSymbol(symbols, SymbolKind.Field, "x") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "x") + assertSymbol(symbols, SymbolKind.Parameter, "y") - assertNoSymbols(symbols) - } + assertProgramSymbol(symbols) + assertMainSymbol(symbols) - test("fields") { - val comp = mkCompilation( - "class Foo() {\n" + - " var z = 0\n" + - "}" - ) + assertNoSymbols(symbols) + } - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Class, "Foo") - assertSymbol(symbols, SymbolKind.Field, "z") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - } - } + it should "bind class fields" in { + val comp = mkCompilation( + "class Foo() {\n" + + " var z = 0\n" + + "}" + ) - test("enums") { - test("without args") { - val comp = mkCompilation( - "enum Foo {\n" + - " case Bar\n" + - " case Baz\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Foo") - assertSymbol(symbols, SymbolKind.Class, "Bar") - assertSymbol(symbols, SymbolKind.Class, "Baz") + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Class, "Foo") + assertSymbol(symbols, SymbolKind.Field, "z") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + } - assertProgramSymbol(symbols) - assertMainSymbol(symbols) + it should "bind enums without args" in { + val comp = mkCompilation( + "enum Foo {\n" + + " case Bar\n" + + " case Baz\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Foo") + assertSymbol(symbols, SymbolKind.Class, "Bar") + assertSymbol(symbols, SymbolKind.Class, "Baz") - assertNoSymbols(symbols) - } + assertProgramSymbol(symbols) + assertMainSymbol(symbols) + + assertNoSymbols(symbols) + } - test("with args") { - val comp = mkCompilation( - "enum Foo {\n" + - " case Bar(x: int)\n" + - " case Baz(y: int)\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Foo") - assertSymbol(symbols, SymbolKind.Class, "Bar") - assertSymbol(symbols, SymbolKind.Field, "x") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "x") + it should "bind enums with args" in { + val comp = mkCompilation( + "enum Foo {\n" + + " case Bar(x: int)\n" + + " case Baz(y: int)\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Foo") + assertSymbol(symbols, SymbolKind.Class, "Bar") + assertSymbol(symbols, SymbolKind.Field, "x") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "x") - assertSymbol(symbols, SymbolKind.Class, "Baz") - assertSymbol(symbols, SymbolKind.Field, "y") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "y") + assertSymbol(symbols, SymbolKind.Class, "Baz") + assertSymbol(symbols, SymbolKind.Field, "y") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "y") - assertProgramSymbol(symbols) - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertMainSymbol(symbols) - assertNoSymbols(symbols) - } - } + assertNoSymbols(symbols) + } - test("pattern matching binding") { - test("extract patterns with variables create locals") { - val comp = mkCompilation( - "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}\n" + - "val opt = Option.Some(42)\n" + - "val result = opt match {\n" + - " case Option.Some(value) => value\n" + - " case Option.None => 0\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Option") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Class, "Some") - assertSymbol(symbols, SymbolKind.Field, "value") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "value") + it should "create locals for extract patterns with variables" in { + val comp = mkCompilation( + "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}\n" + + "val opt = Option.Some(42)\n" + + "val result = opt match {\n" + + " case Option.Some(value) => value\n" + + " case Option.None => 0\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Option") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Class, "Some") + assertSymbol(symbols, SymbolKind.Field, "value") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "value") - assertSymbol(symbols, SymbolKind.Class, "None") + assertSymbol(symbols, SymbolKind.Class, "None") - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "result") - assertSymbol(symbols, SymbolKind.Field, "opt") - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "result") + assertSymbol(symbols, SymbolKind.Field, "opt") + assertMainSymbol(symbols) - // Look for pattern variables among remaining symbols - var foundPatternVariable = false - while (symbols.moveNext()) { - val symbol = symbols.current() - if (symbol.kind == SymbolKind.Local && symbol.name == "value") { - foundPatternVariable = true - } - } - assert(foundPatternVariable) + // Look for pattern variables among remaining symbols + var foundPatternVariable = false + while (symbols.moveNext()) { + val symbol = symbols.current() + if (symbol.kind == SymbolKind.Local && symbol.name == "value") { + foundPatternVariable = true } + } + foundPatternVariable shouldBe true + } - test("discard patterns don't create named locals") { - val comp = mkCompilation( - "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}\n" + - "val opt = Option.Some(42)\n" + - "val result = opt match {\n" + - " case Option.Some(_) => 1\n" + - " case Option.None => 0\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Option") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Class, "Some") - assertSymbol(symbols, SymbolKind.Field, "value") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "value") + it should "not create named locals for discard patterns" in { + val comp = mkCompilation( + "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}\n" + + "val opt = Option.Some(42)\n" + + "val result = opt match {\n" + + " case Option.Some(_) => 1\n" + + " case Option.None => 0\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Option") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Class, "Some") + assertSymbol(symbols, SymbolKind.Field, "value") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "value") - assertSymbol(symbols, SymbolKind.Class, "None") + assertSymbol(symbols, SymbolKind.Class, "None") - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "result") - assertSymbol(symbols, SymbolKind.Field, "opt") - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "result") + assertSymbol(symbols, SymbolKind.Field, "opt") + assertMainSymbol(symbols) - // Ensure no local symbol named "_" was created (generated temporaries like $1, $2 are ok) - while (symbols.moveNext()) { - val symbol = symbols.current() - if (symbol.kind == SymbolKind.Local && symbol.name == "_") { - assert(symbol.name != "_") - } - } + // Ensure no local symbol named "_" was created (generated temporaries like $1, $2 are ok) + while (symbols.moveNext()) { + val symbol = symbols.current() + if (symbol.kind == SymbolKind.Local && symbol.name == "_") { + symbol.name should not be "_" } + } + } - test("mixed extract and discard patterns") { - val comp = mkCompilation( - "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}\n" + - "val opt = Option.Some(42)\n" + - "val result = opt match {\n" + - " case Option.Some(x) => x\n" + - " case Option.None => 0\n" + - " case _ => -1\n" + - "}" - ) - val symbols = enumNonBuiltinSymbols(comp) - assertSymbol(symbols, SymbolKind.Alias, "Option") - assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") - assertSymbol(symbols, SymbolKind.Class, "Some") - assertSymbol(symbols, SymbolKind.Field, "value") - assertSymbol(symbols, SymbolKind.Constructor, ".ctor") - assertSymbol(symbols, SymbolKind.Parameter, "value") + it should "handle mixed extract and discard patterns" in { + val comp = mkCompilation( + "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}\n" + + "val opt = Option.Some(42)\n" + + "val result = opt match {\n" + + " case Option.Some(x) => x\n" + + " case Option.None => 0\n" + + " case _ => -1\n" + + "}" + ) + val symbols = enumNonBuiltinSymbols(comp) + assertSymbol(symbols, SymbolKind.Alias, "Option") + assertSymbol(symbols, SymbolKind.TypeParameter(Variance.Invariant), "T") + assertSymbol(symbols, SymbolKind.Class, "Some") + assertSymbol(symbols, SymbolKind.Field, "value") + assertSymbol(symbols, SymbolKind.Constructor, ".ctor") + assertSymbol(symbols, SymbolKind.Parameter, "value") - assertSymbol(symbols, SymbolKind.Class, "None") + assertSymbol(symbols, SymbolKind.Class, "None") - assertProgramSymbol(symbols) - assertSymbol(symbols, SymbolKind.Field, "result") - assertSymbol(symbols, SymbolKind.Field, "opt") - assertMainSymbol(symbols) + assertProgramSymbol(symbols) + assertSymbol(symbols, SymbolKind.Field, "result") + assertSymbol(symbols, SymbolKind.Field, "opt") + assertMainSymbol(symbols) - // Look for the pattern variable 'x' but not for any '_' locals - var foundPatternVariable = false - while (symbols.moveNext()) { - val symbol = symbols.current() - if (symbol.kind == SymbolKind.Local && symbol.name == "x") { - foundPatternVariable = true - } - if (symbol.kind == SymbolKind.Local && symbol.name == "_") { - assert(symbol.name != "_") - } - } - assert(foundPatternVariable) + // Look for the pattern variable 'x' but not for any '_' locals + var foundPatternVariable = false + while (symbols.moveNext()) { + val symbol = symbols.current() + if (symbol.kind == SymbolKind.Local && symbol.name == "x") { + foundPatternVariable = true + } + if (symbol.kind == SymbolKind.Local && symbol.name == "_") { + symbol.name should not be "_" } } + foundPatternVariable shouldBe true } } diff --git a/test/src/test/scala/LexerTests.scala b/test/src/test/scala/LexerTests.scala index 8152460..c9a1fc5 100644 --- a/test/src/test/scala/LexerTests.scala +++ b/test/src/test/scala/LexerTests.scala @@ -1,9 +1,7 @@ import panther.{assert => _, *} -import utest._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -// ScalaTest version class LexerTests extends AnyFlatSpec with Matchers { def mkTokens(text: string): Array[SyntaxToken] = { val sourceFile = new SourceFile(text, "test.pn") diff --git a/test/src/test/scala/MetadataTests.scala b/test/src/test/scala/MetadataTests.scala index 0227736..514055a 100644 --- a/test/src/test/scala/MetadataTests.scala +++ b/test/src/test/scala/MetadataTests.scala @@ -1,7 +1,8 @@ -import panther.{assert => _, *} -import utest._ +import panther.* +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers -object MetadataTests extends TestSuite { +class MetadataTests extends AnyFlatSpec with Matchers { val metadata: Metadata = Metadata() val point: TypeDefToken = metadata.addTypeDef("Point", "", MetadataFlags.None) @@ -53,52 +54,29 @@ object MetadataTests extends TestSuite { val circleRadius: FieldToken = metadata.addField("radius", MetadataFlags.None, 1, 0) - val tests = Tests { - test("findTypeDefForMethod") { - assert(point.token == metadata.findTypeDefForMethod(pointCtor).token) - assert(point.token == metadata.findTypeDefForMethod(pointGetX).token) - assert(point.token == metadata.findTypeDefForMethod(pointGetY).token) - assert( - point3.token == - metadata.findTypeDefForMethod(point3Ctor).token - ) - assert( - point3.token == - metadata.findTypeDefForMethod(point3GetX).token - ) - assert( - point3.token == - metadata.findTypeDefForMethod(point3GetY).token - ) - assert( - point3.token == - metadata.findTypeDefForMethod(point3GetZ).token - ) - assert( - circle.token == - metadata.findTypeDefForMethod(circleCtor).token - ) - assert( - circle.token == - metadata.findTypeDefForMethod(circleGetCenter).token - ) - assert( - circle.token == - metadata.findTypeDefForMethod(circleGetRadius).token - ) - } + "Metadata" should "find type def for methods" in { + point.token shouldBe metadata.findTypeDefForMethod(pointCtor).token + point.token shouldBe metadata.findTypeDefForMethod(pointGetX).token + point.token shouldBe metadata.findTypeDefForMethod(pointGetY).token + point3.token shouldBe metadata.findTypeDefForMethod(point3Ctor).token + point3.token shouldBe metadata.findTypeDefForMethod(point3GetX).token + point3.token shouldBe metadata.findTypeDefForMethod(point3GetY).token + point3.token shouldBe metadata.findTypeDefForMethod(point3GetZ).token + circle.token shouldBe metadata.findTypeDefForMethod(circleCtor).token + circle.token shouldBe metadata.findTypeDefForMethod(circleGetCenter).token + circle.token shouldBe metadata.findTypeDefForMethod(circleGetRadius).token + } - test("getMethodParameterCount") { - assert(metadata.getMethodParameterCount(pointCtor) == 2) - assert(metadata.getMethodParameterCount(pointGetX) == 0) - assert(metadata.getMethodParameterCount(pointGetY) == 0) - assert(metadata.getMethodParameterCount(point3Ctor) == 3) - assert(metadata.getMethodParameterCount(point3GetX) == 0) - assert(metadata.getMethodParameterCount(point3GetY) == 0) - assert(metadata.getMethodParameterCount(point3GetZ) == 0) - assert(metadata.getMethodParameterCount(circleCtor) == 2) - assert(metadata.getMethodParameterCount(circleGetCenter) == 0) - assert(metadata.getMethodParameterCount(circleGetRadius) == 0) - } + it should "get method parameter count" in { + metadata.getMethodParameterCount(pointCtor) shouldBe 2 + metadata.getMethodParameterCount(pointGetX) shouldBe 0 + metadata.getMethodParameterCount(pointGetY) shouldBe 0 + metadata.getMethodParameterCount(point3Ctor) shouldBe 3 + metadata.getMethodParameterCount(point3GetX) shouldBe 0 + metadata.getMethodParameterCount(point3GetY) shouldBe 0 + metadata.getMethodParameterCount(point3GetZ) shouldBe 0 + metadata.getMethodParameterCount(circleCtor) shouldBe 2 + metadata.getMethodParameterCount(circleGetCenter) shouldBe 0 + metadata.getMethodParameterCount(circleGetRadius) shouldBe 0 } } diff --git a/test/src/test/scala/ParserTests.scala b/test/src/test/scala/ParserTests.scala index 5c55f3d..04acd8f 100644 --- a/test/src/test/scala/ParserTests.scala +++ b/test/src/test/scala/ParserTests.scala @@ -1,279 +1,269 @@ -import panther.{assert => _, *} -import TestHelpers._ -import utest._ - -object ParserTests extends TestSuite { - val tests = Tests { - test("expressions") { - test("binary") { - val expr = mkBinaryExpr("1 + 2") - assertNumberExpr(1, expr.left) - assertNumberExpr(2, expr.right) - assertTokenKind(SyntaxKind.PlusToken, expr.operator) - } - - test("unary") { - val expr = mkUnaryExpr("-1") - assertTokenKind(SyntaxKind.DashToken, expr.operator) - assertNumberExpr(1, expr.expression) - } - - test("groups") { - val expr = mkGroupExpr("(12)") - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - assertNumberExpr(12, expr.expression) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - } - - test("precedence") { - val expr = mkBinaryExpr("1 + 2 * 3") - assertNumberExpr(1, expr.left) - assertTokenKind(SyntaxKind.PlusToken, expr.operator) - val right = assertBinaryExpr(expr.right) - assertNumberExpr(2, right.left) - assertTokenKind(SyntaxKind.StarToken, right.operator) - assertNumberExpr(3, right.right) - } - - test("associativity") { - val expr = mkBinaryExpr("1 - 2 - 3") - val left = assertBinaryExpr(expr.left) - assertNumberExpr(1, left.left) - assertTokenKind(SyntaxKind.DashToken, left.operator) - assertNumberExpr(2, left.right) - assertTokenKind(SyntaxKind.DashToken, expr.operator) - assertNumberExpr(3, expr.right) - } - - test("assignment") { - val expr = mkAssignmentExpr("a = 1") - assertTokenKind(SyntaxKind.EqualsToken, expr.equals) - assertNumberExpr(1, expr.right) - assertIdentifierExpr("a", expr.left) - } - - test("calls") { - test("with no arguments") { - val expr = mkCallExpr("f()") - assertIdentifierExpr("f", expr.name) - assertNone(expr.genericArguments) - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - assertEmpty(expr.arguments.expressions) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - } - - test("with one argument") { - val expr = mkCallExpr("f(1)") - assertIdentifierExpr("f", expr.name) - assertNone(expr.genericArguments) - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - val arg = assertSingle(expr.arguments.expressions) - assertNumberExpr(1, arg.expression) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - } - - test("with multiple arguments") { - val expr = mkCallExpr("f(1, 2)") - assertIdentifierExpr("f", expr.name) - val args = expr.arguments - assertNumberExpr(1, assertIndex(0, args.expressions).expression) - assertNumberExpr(2, assertIndex(1, args.expressions).expression) - } - - test("with generic arguments") { - val expr = mkCallExpr("f[int](1)") - val ident = assertGenericIdentifierExpr(expr.name) - assertTokenText("f", ident.identifier) - assertTokenKind( - SyntaxKind.OpenBracketToken, - ident.typeArgumentlist.lessThanToken - ) - assert(ident.typeArgumentlist.arguments.length == 1) - val genArg = ident.typeArgumentlist.arguments(0) - assertName("int", genArg.name) - assertNone(genArg.separator) - assertTokenKind( - SyntaxKind.CloseBracketToken, - ident.typeArgumentlist.greaterThanToken - ) - - val args = expr.arguments - assertNumberExpr(1, assertSingle(args.expressions).expression) - } - } - - test("ifs") { - val expr = mkIfExpr("if (true) 1 else 2") - assertTokenKind(SyntaxKind.IfKeyword, expr.ifKeyword) - assertTrueExpr(expr.condition) - assertNumberExpr(1, expr.thenExpr) - - val elseExpr = assertSome(expr.elseExpr) - assertTokenKind(SyntaxKind.ElseKeyword, elseExpr.elseKeyword) - assertNumberExpr(2, elseExpr.expression) - } - - test("whiles") { - val expr = mkWhileExpr("while (true) 1") - assertTokenKind(SyntaxKind.WhileKeyword, expr.whileKeyword) - assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) - assertTrueExpr(expr.condition) - assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) - assertNumberExpr(1, expr.body) - } - - test("blocks") { - test("with expression") { - val expr = mkBlockExpr("{ 1 }") - assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) - assertEmpty(expr.block.statements) - val blockExpr = assertSome(expr.block.expression) - assertNumberExpr(1, blockExpr) - assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) - } - - test("with statement") { - val expr = mkBlockExpr( - "{\n" + - " val a = 1\n" + - " a\n" + - "}" - ) - // { - assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) - - // val a = 1 - val statements = expr.block.statements - val statement = assertSingle(statements) - val declaration = assertVariableDeclaration(statement) - assertTokenKind(SyntaxKind.ValKeyword, declaration.valOrVarKeyword) - assertTokenText("a", declaration.identifier) - assertTokenKind(SyntaxKind.EqualsToken, declaration.equalToken) - assertNumberExpr(1, declaration.expression) - - // a - val blockExpr = assertSome(expr.block.expression) - assertIdentifierExpr("a", blockExpr) - - // } - assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) - } - } - - test("dot on new line") { - val expr = mkMemberAccessExpr("a\n.b") - assertIdentifierExpr("a", expr.left) - assertTokenKind(SyntaxKind.DotToken, expr.dotToken) - assertSimpleNameIdentifierExpr("b", expr.right) - } - - test("matches") { - test("simple match") { - val expr = mkMatchExpr("1 match { case 1 => 2 }") - assertTokenKind(SyntaxKind.MatchKeyword, expr.matchKeyword) - assertNumberExpr(1, expr.expression) - - val kase = expr.cases.head - assertTokenKind(SyntaxKind.CaseKeyword, kase.caseKeyword) - val pattern = assertLiteralPattern(kase.pattern) - - assertNumberToken(1, pattern.value) - assertTokenKind(SyntaxKind.EqualsGreaterThanToken, kase.arrow) - assertNumberExpr(2, assertSome(kase.block.expression)) - } - - test("empty match case") { - val expr = mkSyntaxTree( - "enum Test {\n" + - " case Empty()\n" + - " case One(one: int)\n" + - "}\n" + - "\n" + - "x match {\n" + - " case Test.Empty() => 2\n" + - " case Test.One(1) => 3\n" + - "}" - ) - assert(expr.diagnostics.count() == 0) - } - } - - test("is expressions") { - val expr = mkIsExpr("x is int") - assertIdentifierExpr("x", expr.expression) - assertTokenKind(SyntaxKind.IsKeyword, expr.isKeyword) - // TODO: add assertion for the type name - } - } - - test("functions") { - test("with no parameters") { - val fn = mkFunctionMember("def f() = { 1 }") - assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) - assertTokenText("f", fn.identifier) - assertNone(fn.genericParameters) - assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) - assertEmpty(fn.parameters) - assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) - assertNone(fn.typeAnnotation) - - val body = assertSome(fn.body) - assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) - val block = assertBlockExpr(body.expression) - val expr = assertSome(block.block.expression) - assertNumberExpr(1, expr) - } - - test("with parameters") { - val fn = mkFunctionMember("def f(a: int, b: int) = { a + b }") - assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) - assertTokenText("f", fn.identifier) - assertNone(fn.genericParameters) - assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) - - val parameters = fn.parameters - val a = assertIndex(0, parameters) - assertTokenText("a", a.identifier) - assertTokenKind(SyntaxKind.ColonToken, a.typeAnnotation.colonToken) - assertName("int", a.typeAnnotation.typ) - - val b = assertIndex(1, parameters) - assertTokenText("b", b.identifier) - assertTokenKind(SyntaxKind.ColonToken, b.typeAnnotation.colonToken) - assertName("int", b.typeAnnotation.typ) - - assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) - assertNone(fn.typeAnnotation) - - val body = assertSome(fn.body) - assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) - val block = assertBlockExpr(body.expression) - val expr = assertSome(block.block.expression) - val binary = assertBinaryExpr(expr) - assertIdentifierExpr("a", binary.left) - assertTokenKind(SyntaxKind.PlusToken, binary.operator) - assertIdentifierExpr("b", binary.right) - } - - test("with return type") { - val fn = mkFunctionMember("def f(): int = { 1 }") - assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) - assertTokenText("f", fn.identifier) - assertNone(fn.genericParameters) - assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) - assertEmpty(fn.parameters) - assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) - - val typeAnnotation = assertSome(fn.typeAnnotation) - assertTokenKind(SyntaxKind.ColonToken, typeAnnotation.colonToken) - assertName("int", typeAnnotation.typ) - - val body = assertSome(fn.body) - assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) - val block = assertBlockExpr(body.expression) - val expr = assertSome(block.block.expression) - assertNumberExpr(1, expr) - } - } +import panther.* +import TestHelpers.* +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ParserTests extends AnyFlatSpec with Matchers { + + "Parser" should "parse binary expressions" in { + val expr = mkBinaryExpr("1 + 2") + assertNumberExpr(1, expr.left) + assertNumberExpr(2, expr.right) + assertTokenKind(SyntaxKind.PlusToken, expr.operator) + } + + it should "parse unary expressions" in { + val expr = mkUnaryExpr("-1") + assertTokenKind(SyntaxKind.DashToken, expr.operator) + assertNumberExpr(1, expr.expression) + } + + it should "parse grouped expressions" in { + val expr = mkGroupExpr("(12)") + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + assertNumberExpr(12, expr.expression) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + } + + it should "handle operator precedence" in { + val expr = mkBinaryExpr("1 + 2 * 3") + assertNumberExpr(1, expr.left) + assertTokenKind(SyntaxKind.PlusToken, expr.operator) + val right = assertBinaryExpr(expr.right) + assertNumberExpr(2, right.left) + assertTokenKind(SyntaxKind.StarToken, right.operator) + assertNumberExpr(3, right.right) + } + + it should "handle operator associativity" in { + val expr = mkBinaryExpr("1 - 2 - 3") + val left = assertBinaryExpr(expr.left) + assertNumberExpr(1, left.left) + assertTokenKind(SyntaxKind.DashToken, left.operator) + assertNumberExpr(2, left.right) + assertTokenKind(SyntaxKind.DashToken, expr.operator) + assertNumberExpr(3, expr.right) + } + + it should "parse assignment expressions" in { + val expr = mkAssignmentExpr("a = 1") + assertTokenKind(SyntaxKind.EqualsToken, expr.equals) + assertNumberExpr(1, expr.right) + assertIdentifierExpr("a", expr.left) + } + + it should "parse call expressions with no arguments" in { + val expr = mkCallExpr("f()") + assertIdentifierExpr("f", expr.name) + assertNone(expr.genericArguments) + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + assertEmpty(expr.arguments.expressions) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + } + + it should "parse call expressions with one argument" in { + val expr = mkCallExpr("f(1)") + assertIdentifierExpr("f", expr.name) + assertNone(expr.genericArguments) + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + val arg = assertSingle(expr.arguments.expressions) + assertNumberExpr(1, arg.expression) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + } + + it should "parse call expressions with multiple arguments" in { + val expr = mkCallExpr("f(1, 2)") + assertIdentifierExpr("f", expr.name) + val args = expr.arguments + assertNumberExpr(1, assertIndex(0, args.expressions).expression) + assertNumberExpr(2, assertIndex(1, args.expressions).expression) + } + + it should "parse call expressions with generic arguments" in { + val expr = mkCallExpr("f[int](1)") + val ident = assertGenericIdentifierExpr(expr.name) + assertTokenText("f", ident.identifier) + assertTokenKind( + SyntaxKind.OpenBracketToken, + ident.typeArgumentlist.lessThanToken + ) + ident.typeArgumentlist.arguments.length shouldBe 1 + val genArg = ident.typeArgumentlist.arguments(0) + assertName("int", genArg.name) + assertNone(genArg.separator) + assertTokenKind( + SyntaxKind.CloseBracketToken, + ident.typeArgumentlist.greaterThanToken + ) + + val args = expr.arguments + assertNumberExpr(1, assertSingle(args.expressions).expression) + } + + it should "parse if expressions" in { + val expr = mkIfExpr("if (true) 1 else 2") + assertTokenKind(SyntaxKind.IfKeyword, expr.ifKeyword) + assertTrueExpr(expr.condition) + assertNumberExpr(1, expr.thenExpr) + + val elseExpr = assertSome(expr.elseExpr) + assertTokenKind(SyntaxKind.ElseKeyword, elseExpr.elseKeyword) + assertNumberExpr(2, elseExpr.expression) + } + + it should "parse while expressions" in { + val expr = mkWhileExpr("while (true) 1") + assertTokenKind(SyntaxKind.WhileKeyword, expr.whileKeyword) + assertTokenKind(SyntaxKind.OpenParenToken, expr.openParen) + assertTrueExpr(expr.condition) + assertTokenKind(SyntaxKind.CloseParenToken, expr.closeParen) + assertNumberExpr(1, expr.body) + } + + it should "parse block expressions with expression" in { + val expr = mkBlockExpr("{ 1 }") + assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) + assertEmpty(expr.block.statements) + val blockExpr = assertSome(expr.block.expression) + assertNumberExpr(1, blockExpr) + assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) + } + + it should "parse block expressions with statement" in { + val expr = mkBlockExpr( + "{\n" + + " val a = 1\n" + + " a\n" + + "}" + ) + // { + assertTokenKind(SyntaxKind.OpenBraceToken, expr.openBrace) + + // val a = 1 + val statements = expr.block.statements + val statement = assertSingle(statements) + val declaration = assertVariableDeclaration(statement) + assertTokenKind(SyntaxKind.ValKeyword, declaration.valOrVarKeyword) + assertTokenText("a", declaration.identifier) + assertTokenKind(SyntaxKind.EqualsToken, declaration.equalToken) + assertNumberExpr(1, declaration.expression) + + // a + val blockExpr = assertSome(expr.block.expression) + assertIdentifierExpr("a", blockExpr) + + // } + assertTokenKind(SyntaxKind.CloseBraceToken, expr.closeBrace) + } + + it should "parse dot on new line" in { + val expr = mkMemberAccessExpr("a\n.b") + assertIdentifierExpr("a", expr.left) + assertTokenKind(SyntaxKind.DotToken, expr.dotToken) + assertSimpleNameIdentifierExpr("b", expr.right) + } + + it should "parse simple match expressions" in { + val expr = mkMatchExpr("1 match { case 1 => 2 }") + assertTokenKind(SyntaxKind.MatchKeyword, expr.matchKeyword) + assertNumberExpr(1, expr.expression) + + val kase = expr.cases.head + assertTokenKind(SyntaxKind.CaseKeyword, kase.caseKeyword) + val pattern = assertLiteralPattern(kase.pattern) + + assertNumberToken(1, pattern.value) + assertTokenKind(SyntaxKind.EqualsGreaterThanToken, kase.arrow) + assertNumberExpr(2, assertSome(kase.block.expression)) + } + + it should "parse empty match case" in { + val expr = mkSyntaxTree( + "enum Test {\n" + + " case Empty()\n" + + " case One(one: int)\n" + + "}\n" + + "\n" + + "x match {\n" + + " case Test.Empty() => 2\n" + + " case Test.One(1) => 3\n" + + "}" + ) + expr.diagnostics.count() shouldBe 0 + } + + it should "parse is expressions" in { + val expr = mkIsExpr("x is int") + assertIdentifierExpr("x", expr.expression) + assertTokenKind(SyntaxKind.IsKeyword, expr.isKeyword) + // TODO: add assertion for the type name + } + + it should "parse functions with no parameters" in { + val fn = mkFunctionMember("def f() = { 1 }") + assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) + assertTokenText("f", fn.identifier) + assertNone(fn.genericParameters) + assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) + assertEmpty(fn.parameters) + assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) + assertNone(fn.typeAnnotation) + + val body = assertSome(fn.body) + assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) + val block = assertBlockExpr(body.expression) + val expr = assertSome(block.block.expression) + assertNumberExpr(1, expr) + } + + it should "parse functions with parameters" in { + val fn = mkFunctionMember("def f(a: int, b: int) = { a + b }") + assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) + assertTokenText("f", fn.identifier) + assertNone(fn.genericParameters) + assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) + + val parameters = fn.parameters + val a = assertIndex(0, parameters) + assertTokenText("a", a.identifier) + assertTokenKind(SyntaxKind.ColonToken, a.typeAnnotation.colonToken) + assertName("int", a.typeAnnotation.typ) + + val b = assertIndex(1, parameters) + assertTokenText("b", b.identifier) + assertTokenKind(SyntaxKind.ColonToken, b.typeAnnotation.colonToken) + assertName("int", b.typeAnnotation.typ) + + assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) + assertNone(fn.typeAnnotation) + + val body = assertSome(fn.body) + assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) + val block = assertBlockExpr(body.expression) + val expr = assertSome(block.block.expression) + val binary = assertBinaryExpr(expr) + assertIdentifierExpr("a", binary.left) + assertTokenKind(SyntaxKind.PlusToken, binary.operator) + assertIdentifierExpr("b", binary.right) + } + + it should "parse functions with return type" in { + val fn = mkFunctionMember("def f(): int = { 1 }") + assertTokenKind(SyntaxKind.DefKeyword, fn.defKeyword) + assertTokenText("f", fn.identifier) + assertNone(fn.genericParameters) + assertTokenKind(SyntaxKind.OpenParenToken, fn.openParenToken) + assertEmpty(fn.parameters) + assertTokenKind(SyntaxKind.CloseParenToken, fn.closeParenToken) + + val typeAnnotation = assertSome(fn.typeAnnotation) + assertTokenKind(SyntaxKind.ColonToken, typeAnnotation.colonToken) + assertName("int", typeAnnotation.typ) + + val body = assertSome(fn.body) + assertTokenKind(SyntaxKind.EqualsToken, body.equalToken) + val block = assertBlockExpr(body.expression) + val expr = assertSome(block.block.expression) + assertNumberExpr(1, expr) } } diff --git a/test/src/test/scala/TestHelpers.scala b/test/src/test/scala/TestHelpers.scala index 6b87d2d..59b1dd3 100644 --- a/test/src/test/scala/TestHelpers.scala +++ b/test/src/test/scala/TestHelpers.scala @@ -1,5 +1,4 @@ import panther.{assert => panthAssert, *} -import utest._ object TestHelpers { @@ -19,7 +18,7 @@ object TestHelpers { ) if (comp.diagnostics.count() > 0) { comp.diagnostics.printDiagnostics(20) - throw new AssertionError("Compilation failed", Seq()) + throw new AssertionError("Compilation failed") } comp } @@ -85,21 +84,21 @@ object TestHelpers { def mkSyntaxTreeExpr(text: string): Expression = { mkSyntaxTreeStatement(text) match { case StatementSyntax.ExpressionStatement(expr) => expr - case _ => throw new AssertionError("Expected expression", Seq()) + case _ => throw new AssertionError("Expected expression") } } def mkSyntaxTreeStatement(text: string): StatementSyntax = { mkMember(text) match { case MemberSyntax.GlobalStatementSyntax(statement) => statement - case _ => throw new AssertionError("Expected statement", Seq()) + case _ => throw new AssertionError("Expected statement") } } def mkFunctionMember(text: string): MemberSyntax.FunctionDeclarationSyntax = { mkMember(text) match { case member: MemberSyntax.FunctionDeclarationSyntax => member - case _ => throw new AssertionError("Expected function declaration", Seq()) + case _ => throw new AssertionError("Expected function declaration") } } @@ -111,38 +110,36 @@ object TestHelpers { def assertSingle[T](items: List[T]): T = { items match { case List.Nil => - throw new AssertionError("expected one item, found zero", Seq()) + throw new AssertionError("expected one item, found zero") case List.Cons(head, tail) => if (tail.isEmpty) head else throw new AssertionError( - "expected one item, found " + items.length, - Seq() + "expected one item, found " + items.length ) } } def assertSingle[T](items: Array[T]): T = { if (items.length == 0) - throw new AssertionError("expected one item, found zero", Seq()) + throw new AssertionError("expected one item, found zero") else if (items.length > 1) throw new AssertionError( - "expected one item, found " + items.length, - Seq() + "expected one item, found " + items.length ) else items(0) } def assertIndex[T](i: int, list: List[T]): T = { if (i < 0 || i >= list.length) - throw new AssertionError("index out of bounds: " + i, Seq()) + throw new AssertionError("index out of bounds: " + i) list.getUnsafe(i) } def assertBinaryExpr(expression: Expression): Expression.Binary = { expression match { case expr: Expression.Binary => expr - case _ => throw new AssertionError("expected binary expression", Seq()) + case _ => throw new AssertionError("expected binary expression") } } @@ -152,7 +149,7 @@ object TestHelpers { expression match { case expr: Expression.MemberAccess => expr case _ => - throw new AssertionError("expected member access expression", Seq()) + throw new AssertionError("expected member access expression") } } @@ -161,7 +158,7 @@ object TestHelpers { ): Expression.Match = { expression match { case expr: Expression.Match => expr - case _ => throw new AssertionError("expected match expression", Seq()) + case _ => throw new AssertionError("expected match expression") } } @@ -170,7 +167,7 @@ object TestHelpers { ): PatternSyntax.Literal = { expression match { case expr: PatternSyntax.Literal => expr - case _ => throw new AssertionError("expected literal pattern", Seq()) + case _ => throw new AssertionError("expected literal pattern") } } @@ -180,7 +177,7 @@ object TestHelpers { expression match { case expr: Expression.Assignment => expr case _ => - throw new AssertionError("expected assignment expression", Seq()) + throw new AssertionError("expected assignment expression") } } @@ -189,7 +186,7 @@ object TestHelpers { ): Expression.If = { expression match { case expr: Expression.If => expr - case _ => throw new AssertionError("expected if expression", Seq()) + case _ => throw new AssertionError("expected if expression") } } @@ -198,7 +195,7 @@ object TestHelpers { ): Expression.Call = { expression match { case expr: Expression.Call => expr - case _ => throw new AssertionError("expected call expression", Seq()) + case _ => throw new AssertionError("expected call expression") } } @@ -207,35 +204,35 @@ object TestHelpers { ): Expression.While = { expression match { case expr: Expression.While => expr - case _ => throw new AssertionError("expected while expression", Seq()) + case _ => throw new AssertionError("expected while expression") } } def assertGroupExpr(expr: Expression): Expression.Group = { expr match { case expr: Expression.Group => expr - case _ => throw new AssertionError("expected group expression", Seq()) + case _ => throw new AssertionError("expected group expression") } } def assertBlockExpr(expr: Expression): Expression.Block = { expr match { case expr: Expression.Block => expr - case _ => throw new AssertionError("expected block expression", Seq()) + case _ => throw new AssertionError("expected block expression") } } def assertUnaryExpr(expr: Expression): Expression.Unary = { expr match { case expr: Expression.Unary => expr - case _ => throw new AssertionError("expected unary expression", Seq()) + case _ => throw new AssertionError("expected unary expression") } } def assertIsExpr(expr: Expression): Expression.Is = { expr match { case expr: Expression.Is => expr - case _ => throw new AssertionError("expected is expression", Seq()) + case _ => throw new AssertionError("expected is expression") } } @@ -244,7 +241,7 @@ object TestHelpers { ): StatementSyntax.VariableDeclarationStatement = { statement match { case stmt: StatementSyntax.VariableDeclarationStatement => stmt - case _ => throw new AssertionError("Expected variable declaration", Seq()) + case _ => throw new AssertionError("Expected variable declaration") } } @@ -257,10 +254,7 @@ object TestHelpers { case SyntaxTokenValue.Number(value) => assert(value == expected) case _ => - throw new AssertionError( - "Expected number token, got: " + token.value, - Seq() - ) + throw new AssertionError("Expected number token, got: " + token.value) } } @@ -268,7 +262,7 @@ object TestHelpers { expression match { case Expression.Literal(_, SyntaxTokenValue.Number(n)) => assert(n == expected) - case _ => throw new AssertionError("Expected number expression", Seq()) + case _ => throw new AssertionError("Expected number expression") } } @@ -276,21 +270,21 @@ object TestHelpers { expression match { case Expression.Literal(_, SyntaxTokenValue.Boolean(b)) => assert(b == expected) - case _ => throw new AssertionError("Expected boolean expression", Seq()) + case _ => throw new AssertionError("Expected boolean expression") } } def assertTrueExpr(expression: Expression): Unit = { expression match { case Expression.Literal(_, SyntaxTokenValue.Boolean(true)) => - case _ => throw new AssertionError("Expected true expression", Seq()) + case _ => throw new AssertionError("Expected true expression") } } def assertFalseExpr(expression: Expression): Unit = { expression match { case Expression.Literal(_, SyntaxTokenValue.Boolean(false)) => - case _ => throw new AssertionError("Expected false expression", Seq()) + case _ => throw new AssertionError("Expected false expression") } } @@ -301,7 +295,7 @@ object TestHelpers { ) => assert(token.text == expected) case _ => - throw new AssertionError("Expected identifier expression", Seq()) + throw new AssertionError("Expected identifier expression") } } @@ -313,7 +307,7 @@ object TestHelpers { case SimpleNameSyntax.IdentifierNameSyntax(token) => assert(token.text == expected) case _ => - throw new AssertionError("Expected identifier expression", Seq()) + throw new AssertionError("Expected identifier expression") } } @@ -326,10 +320,7 @@ object TestHelpers { ) => a case _ => - throw new AssertionError( - "Expected generic identifier expression", - Seq() - ) + throw new AssertionError("Expected generic identifier expression") } } @@ -337,8 +328,7 @@ object TestHelpers { if (expected != token.kind) throw new AssertionError( "expected " + SyntaxFacts.getKindName(expected) + ", got " + SyntaxFacts - .getKindName(token.kind), - Seq() + .getKindName(token.kind) ) def assertTokenText(expected: string, token: SyntaxToken): Unit = @@ -354,19 +344,18 @@ object TestHelpers { def assertSome[T](option: Option[T]): T = option match { case Option.Some(value) => value case Option.None => - throw new AssertionError("expected Some, found None", Seq()) + throw new AssertionError("expected Some, found None") } def assertNone[T](option: Option[T]): Unit = { if (option.isDefined()) - throw new AssertionError("expected None, found Some", Seq()) + throw new AssertionError("expected None, found Some") } def assertEmpty[T](list: List[T]): Unit = { if (!list.isEmpty) throw new AssertionError( - "expected empty list, found " + list.length + " items", - Seq() + "expected empty list, found " + list.length + " items" ) } @@ -461,8 +450,7 @@ object TestHelpers { if (enumerator.moveNext()) { val symbol = enumerator.current() throw new AssertionError( - "Unexpected symbol: " + symbol.kind + " " + symbol.name, - Seq() + "Unexpected symbol: " + symbol.kind + " " + symbol.name ) } } @@ -513,8 +501,7 @@ object TestHelpers { case Option.None => throw new AssertionError( "Expected symbol " + symbol.name + " to have a type " + typ - + " but got none", - Seq() + + " but got none" ) } } @@ -528,7 +515,7 @@ object TestHelpers { case InterpretResult.OkValue(value) => value case result => - throw new AssertionError("Expected exec result, got: " + result, Seq()) + throw new AssertionError("Expected exec result, got: " + result) } } @@ -538,8 +525,7 @@ object TestHelpers { assert(v == expected) case _ => throw new AssertionError( - "Expected " + string(expected) + ", got: " + value, - Seq() + "Expected " + string(expected) + ", got: " + value ) } } @@ -550,8 +536,7 @@ object TestHelpers { assert(v == expected) case _ => throw new AssertionError( - "Expected " + string(expected) + ", got: " + value, - Seq() + "Expected " + string(expected) + ", got: " + value ) } } @@ -561,7 +546,7 @@ object TestHelpers { case Value.String(v) => assert(v == expected) case _ => - throw new AssertionError("Expected string value, got: " + value, Seq()) + throw new AssertionError("Expected string value, got: " + value) } } diff --git a/test/src/test/scala/TypeTests.scala b/test/src/test/scala/TypeTests.scala index 269a25d..625df7b 100644 --- a/test/src/test/scala/TypeTests.scala +++ b/test/src/test/scala/TypeTests.scala @@ -1,736 +1,621 @@ -import panther.{assert => _, *} -import TestHelpers._ -import utest._ - -object TypeTests extends TestSuite { - val tests = Tests { - test("primitives") { - assertExprTypeTest("12", "int") - assertExprTypeTest("0", "int") - assertExprTypeTest("true", "bool") - assertExprTypeTest("false", "bool") - assertExprTypeTest("\"hello\"", "string") - assertExprTypeTest("'a'", "char") - assertExprTypeTest("()", "unit") - } - - test("binary") { - assertExprTypeTest("1 + 2", "int") - assertExprTypeTest("1 - 2", "int") - assertExprTypeTest("1 * 2", "int") - assertExprTypeTest("1 / 2", "int") - assertExprTypeTest("1 % 2", "int") - assertExprTypeTest("1 == 2", "bool") - assertExprTypeTest("1 != 2", "bool") - assertExprTypeTest("1 < 2", "bool") - assertExprTypeTest("1 <= 2", "bool") - assertExprTypeTest("1 > 2", "bool") - assertExprTypeTest("1 >= 2", "bool") - assertExprTypeTest("true && false", "bool") - assertExprTypeTest("true || false", "bool") - } - - test("unary") { - assertExprTypeTest("-1", "int") - assertExprTypeTest("+1", "int") - // FIXME: assertExprType("~7", "int") - assertExprTypeTest("!true", "bool") - } - - test("groups") { - assertExprTypeTest("(12)", "int") - assertExprTypeTest("(true)", "bool") - } - - test("variables") { - test("int variable") { - assertExprTypeWithSetup("val x = 12", "x", "int") - } - - test("bool variable") { - assertExprTypeWithSetup("val x = true", "x", "bool") - } - - test("string variable") { - assertExprTypeWithSetup("val x = \"hello\"", "x", "string") - } - - test("char variable") { - assertExprTypeWithSetup("val x = 'a'", "x", "char") - } - - test("variable addition") { - assertExprTypeWithSetup("val x = 12", "x + 12", "int") - } - - test("variable equality") { - assertExprTypeWithSetup("val x = 12", "12 == x", "bool") - } - - test("variable assignment") { - assertExprTypeWithSetup("var x = 12", "x = 10", "unit") - } - } - - test("conversions") { - assertAssignableTo("12", "any") - assertAssignableTo("true", "any") - assertAssignableTo("\"hello\"", "any") - assertAssignableTo("'a'", "any") - } - - test("calls") { - assertExprTypeTest("println(12)", "unit") - assertExprTypeTest("print(12)", "unit") - assertExprTypeTest("print(\"hello\")", "unit") - assertExprTypeTest("print('a')", "unit") - assertExprTypeTest("print(true)", "unit") - assertExprTypeTest("print(12 + 12)", "unit") - assertExprTypeTest("print(12 == 12)", "unit") - assertExprTypeTest("print(12 < 12)", "unit") - assertExprTypeTest("print(true && false)", "unit") - assertExprTypeTest("print(true || false)", "unit") - } - - test("casts") { - assertExprTypeTest("'a' as char", "char") - assertExprTypeTest("12 as char", "char") - assertExprTypeTest("'a' as int", "int") - assertExprTypeTest("12 as int", "int") - assertExprTypeTest("'a' as string", "string") - assertExprTypeTest("12 as string", "string") - assertExprTypeTest("\"hello\" as string", "string") - assertExprTypeTest("true as string", "string") - } - - test("is expressions") { - assertExprTypeTest("12 is int", "bool") - assertExprTypeTest("'a' is char", "bool") - assertExprTypeTest("\"hello\" is string", "bool") - assertExprTypeTest("true is bool", "bool") - assertExprTypeTest("12 is bool", "bool") // should return false at runtime - } - - test("blocks") { - assertExprTypeTest("{ 1 }", "int") - assertExprTypeTest("{ true }", "bool") - assertExprTypeTest("{ }", "unit") - assertExprTypeTest( - "{\n" + - " 1\n" + - " 2\n" + - "}", - "int" - ) - assertExprTypeTest( - "{\n" + - "true\n" + - "false\n" + - "}", - "bool" - ) - assertExprTypeTest( - "{\n" + - " 1\n" + - " true\n" + - "}", - "bool" - ) - assertExprTypeTest( - "{\n" + - " true\n" + - " 1\n" + - "}", - "int" - ) - } - - test("ifs") { - assertExprTypeTest("if (true) 1 else 2", "int") - assertExprTypeTest("if (false) 1 else 2", "int") - assertExprTypeTest("if (true) true else false", "bool") - assertExprTypeTest("if (false) true else false", "bool") - assertExprTypeTest("if (false) true", "unit") - } - - test("whiles") { - assertExprTypeTest("while (true) 1", "unit") - assertExprTypeTest("while (false) 1", "unit") - } - - test("matches") { - test("literal patterns") { - assertExprTypeTest("1 match { case 1 => 2 }", "int") - assertExprTypeTest("1 match { case 2 => 3 }", "int") - assertExprTypeTest("true match { case true => false }", "bool") - assertExprTypeTest( - "\"hello\" match { case \"world\" => \"hi\" }", - "string" - ) - assertExprTypeTest("'a' match { case 'b' => 'c' }", "char") - } - - test("wildcard patterns") { - assertExprTypeTest("1 match { case _ => 2 }", "int") - assertExprTypeTest("true match { case _ => false }", "bool") - assertExprTypeTest("\"hello\" match { case _ => \"world\" }", "string") - } - -// test("variable patterns") { -// assertExprTypeTest("1 match { case x: int => x + 1 }", "int") -// assertExprTypeTest("true match { case b: bool => !b }", "bool") -// assertExprTypeTest("\"hello\" match { case s: string => s }", "string") -// } - - test("multiple cases with same type") { - assertExprTypeTest( - "1 match { case 1 => 10\n case 2 => 20 }", - "int" - ) - assertExprTypeTest( - "true match { case true => 1\n case false => 0 }", - "int" - ) - } - - test("multiple cases with different types") { - // When cases have different types, result should be 'any' (least upper bound) - assertExprTypeTest( - "1 match { case 1 => 42\n case 2 => \"hello\" }", - "int | string" - ) - assertExprTypeTest( - "1 match { case 1 => true\n case 2 => 123 }", - "bool | int" - ) - } - - test("unit result cases") { - assertExprTypeTest("1 match { case x: int => () }", "unit") - assertExprTypeTest("1 match { case 1 => println(\"test\") }", "unit") - } - - test("nested matches") { - assertExprTypeTest( - "1 match { case x: int => x match { case y: int => y * 2 } }", - "int" - ) - } - -// test("match in expressions") { -// assertExprTypeTest( -// "(1 match { case x: int => x }) + 5", -// "int" -// ) -// assertExprTypeTest( -// "!(true match { case b: bool => b })", -// "bool" -// ) -// } - - test("match with blocks") { - assertExprTypeTest( - "1 match { case x: int => { val y = x + 1\n y * 2 } }", - "int" - ) - assertExprTypeTest( - "true match { case b: bool => { println(\"test\")\n b } }", - "bool" - ) - } - } - - test("methods") { - test("without return type") { - val comp = mkCompilation("def foo() = 12") - val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - val foo = assertSymbol(symbols, SymbolKind.Method, "foo") - assertSymbolType(comp, foo, "() -> int") - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } - - test("with parameters") { - val comp = mkCompilation("def foo(x: int, y: int) = 12") - val symbols = enumNonBuiltinSymbols(comp) - assertProgramSymbol(symbols) - val foo = assertSymbol(symbols, SymbolKind.Method, "foo") - assertSymbolType(comp, foo, "(x: int, y: int) -> int") - val x = assertSymbol(symbols, SymbolKind.Parameter, "x") - assertSymbolType(comp, x, "int") - val y = assertSymbol(symbols, SymbolKind.Parameter, "y") - assertSymbolType(comp, y, "int") - assertMainSymbol(symbols) - assertNoSymbols(symbols) - } - -// test("with generic arguments") { -// // FIXME: this is throwing an error atm -// val comp = mkCompilation("def foo[T](x: T) = 12") -// val symbols = enumNonBuiltinSymbols(comp) -// val foo = assertSymbol(symbols, SymbolKind.Method, "foo") -// assertSymbolType(comp, foo, "[T](x: T) -> int") -// val x = assertSymbol(symbols, SymbolKind.Parameter, "x") -// assertSymbolType(comp, x, "T") -// assertNoSymbols(symbols) -// } - } - - test("generic methods") { - test("simple identity method (testing basic inference)") { - val setup = "def identity[T](x: T): T = x" - - assertExprTypeWithSetup(setup, "identity(42)", "int") - - assertExprTypeWithSetup(setup, "identity(true)", "bool") - - assertExprTypeWithSetup(setup, "identity(\"hello\")", "string") - - assertExprTypeWithSetup(setup, "identity('a')", "char") - } - - test("method returning concrete type") { - val setup = "def getValue[T](x: T): int = 42" - - // These should work because the return type is concrete - assertExprTypeWithSetup(setup, "getValue(42)", "int") - assertExprTypeWithSetup(setup, "getValue(\"hello\")", "int") - assertExprTypeWithSetup(setup, "getValue(true)", "int") - } - - test("generic parameter with concrete return") { - // Test methods that accept generic parameters but return concrete types - val setup = "def stringify[T](x: T): string = string(x)" - - assertExprTypeWithSetup(setup, "stringify(42)", "string") - assertExprTypeWithSetup(setup, "stringify(true)", "string") - assertExprTypeWithSetup(setup, "stringify('a')", "string") - } - -// test("array element access") { -// val setup = "def first[T](arr: Array[T]): T = arr[0]" -// -// assertExprTypeWithSetup(setup, "first(new Array[int](5))", "int") -// assertExprTypeWithSetup(setup, "first(new Array[bool](3))", "bool") -// assertExprTypeWithSetup(setup, "first(new Array[string](2))", "string") -// assertExprTypeWithSetup(setup, "first(new Array[char](1))", "char") -// } -// - test("generic container creation (commented until generics work)") { - val containerSetup = "class Container[T](value: T)\n" + - "def wrap[T](x: T): Container[T] = new Container(x)" - - assertExprTypeWithSetup(containerSetup, "wrap(42)", "Container") - assertExprTypeWithSetup(containerSetup, "wrap(true)", "Container") - assertExprTypeWithSetup( - containerSetup, - "wrap(\"test\")", - "Container" - ) - } -// -// test("option-like generic methods (commented until generics work)") { -// val optionSetup = "enum Option[T] {\n" + -// " case Some(value: T)\n" + -// " case None\n" + -// "}\n" + -// "def maybe[T](condition: bool, value: T): Option[T] = {\n" + -// " if (condition) Option.Some(value) else Option.None\n" + -// "}" -// -// assertExprTypeWithSetup(optionSetup, "maybe(true, 42)", "Option[int]") -// assertExprTypeWithSetup(optionSetup, "maybe(false, \"hello\")", "Option[string]") -// assertExprTypeWithSetup(optionSetup, "maybe(true, 'x')", "Option[char]") -// } - -// test("multiple type parameters (commented until generics work)") { -// val pairSetup = "class Pair[A, B](first: A, second: B)\n" + -// "def makePair[A, B](a: A, b: B): Pair[A, B] = new Pair(a, b)" -// -// assertExprTypeWithSetup(pairSetup, "makePair(42, \"hello\")", "Pair[int, string]") -// assertExprTypeWithSetup(pairSetup, "makePair(true, 'x')", "Pair[bool, char]") -// assertExprTypeWithSetup(pairSetup, "makePair(1, 2)", "Pair[int, int]") -// } -// -// test("list operations (commented until generics work)") { -// val listSetup = "enum List[T] {\n" + -// " case Cons(head: T, tail: List[T])\n" + -// " case Nil\n" + -// "}\n" + -// "def singleton[T](value: T): List[T] = List.Cons(value, List.Nil)\n" + -// "def prepend[T](value: T, list: List[T]): List[T] = List.Cons(value, list)" -// -// assertExprTypeWithSetup(listSetup, "singleton(42)", "List[int]") -// assertExprTypeWithSetup(listSetup, "singleton(\"test\")", "List[string]") -// assertExprTypeWithSetup(listSetup, "prepend(1, singleton(2))", "List[int]") -// assertExprTypeWithSetup(listSetup, "prepend(true, List.Nil)", "List[bool]") -// } -// -// test("chaining generic calls (commented until generics work)") { -// val setupChain = "def identity[T](x: T): T = x\n" + -// "class Box[T](value: T)\n" + -// "def box[T](x: T): Box[T] = new Box(x)\n" + -// "def unbox[T](b: Box[T]): T = b.value" -// -// assertExprTypeWithSetup(setupChain, "identity(identity(42))", "int") -// assertExprTypeWithSetup(setupChain, "unbox(box(42))", "int") -// assertExprTypeWithSetup(setupChain, "identity(unbox(box(\"hello\")))", "string") -// assertExprTypeWithSetup(setupChain, "box(identity(true))", "Box[bool]") -// } - } - - test("enums") { - test("without args") { - val setup = "enum Foo {\n" + - " case Bar\n" + - " case Baz\n" + - "}" - assertExprTypeWithSetup(setup, "Foo.Bar", "Foo.Bar") - assertExprTypeWithSetup(setup, "Foo.Baz", "Foo.Baz") - } - - test("with args") { - val setup = "enum Foo {\n" + - " case Bar(x: int)\n" + - " case Baz(y: string)\n" + - "}" - assertExprTypeWithSetup(setup, "Foo.Bar(12)", "Foo.Bar") - assertExprTypeWithSetup(setup, "Foo.Baz(\"taco\")", "Foo.Baz") - assertExprTypeWithSetup(setup, "new Foo.Bar(12)", "Foo.Bar") - assertExprTypeWithSetup(setup, "new Foo.Baz(\"taco\")", "Foo.Baz") - } - - test("check assignments") { - val setup = "enum Foo {\n" + - " case Bar(x: int)\n" + - " case Baz(y: string)\n" + - "}" - assertAssignableToWithSetup(setup, "Foo.Bar(12)", "Foo") - assertAssignableToWithSetup(setup, "Foo.Baz(\"taco\")", "Foo") - } - - test("with generic type") { - val setup = "enum Option[T] {\n" + - " case Some(value: T)\n" + - " case None\n" + - "}" - assertAssignableToWithSetup(setup, "new Option.Some(12)", "Option[int]") - assertAssignableToWithSetup( - setup, - "new Option.Some(12)", - "Option.Some[int]" - ) - assertAssignableToWithSetup(setup, "Option.Some(12)", "Option[int]") - assertAssignableToWithSetup(setup, "Option.None", "Option[int]") - assertAssignableToWithSetup(setup, "Option.None", "Option[never]") - } - - test("list examples") { - val setup = "enum List[T] {\n" + - " case Cons(head: T, tail: List[T])\n" + - " case Nil\n" + - "}" - assertAssignableToWithSetup( - setup, - "new List.Cons(1, List.Nil)", - "List[int]" - ) - assertAssignableToWithSetup( - setup, - "List.Cons(1, List.Nil)", - "List[int]" - ) - assertAssignableToWithSetup( - setup, - "new List.Cons(\"hello\", List.Nil)", - "List[string]" - ) - assertAssignableToWithSetup( - setup, - "List.Cons(\"hello\", List.Nil)", - "List[string]" - ) - assertAssignableToWithSetup(setup, "List.Nil", "List[int]") - assertAssignableToWithSetup(setup, "List.Nil", "List[string]") - } - } - - test("classes") { - test("without args") { - val setup = "class Foo()" - assertExprTypeWithSetup(setup, "new Foo()", "Foo") - } - - test("with args") { - val setup = "class Foo(x: int, y: string)" - assertExprTypeWithSetup(setup, "new Foo(12, \"taco\")", "Foo") - } - } - - test("arrays") { -// FIXME: -// test("type") { -// val setup = "val array = new Array[int](0)" -// assertExprTypeWithSetup(setup, "array", "Array[int]") -// } - - test("length type") { - val setup = "val array = new Array[int](0)" - assertExprTypeWithSetup(setup, "array.length", "int") - } - - test("apply with method call") { - val setup = "val array = new Array[int](1)" - assertExprTypeWithSetup(setup, "array.apply(0)", "int") - } - - test("apply with indexer syntax - basic types") { - val setup = "val intArray = new Array[int](1)" - assertExprTypeWithSetup(setup, "intArray(0)", "int") - - val boolSetup = "val boolArray = new Array[bool](1)" - assertExprTypeWithSetup(boolSetup, "boolArray(0)", "bool") - - val stringSetup = "val stringArray = new Array[string](1)" - assertExprTypeWithSetup(stringSetup, "stringArray(0)", "string") - - val charSetup = "val charArray = new Array[char](1)" - assertExprTypeWithSetup(charSetup, "charArray(0)", "char") - } - - test("array indexing in expressions") { - val setup = "val array = new Array[int](5)" - assertExprTypeWithSetup(setup, "array(0) + array(1)", "int") - assertExprTypeWithSetup(setup, "array(2) * 3", "int") - assertExprTypeWithSetup(setup, "array(0) == array(1)", "bool") - } - - test("array indexing with computed indices") { - val setup = "val array = new Array[int](10)\nval i = 5" - assertExprTypeWithSetup(setup, "array(i)", "int") - assertExprTypeWithSetup(setup, "array(1 + 2)", "int") - assertExprTypeWithSetup(setup, "array(i * 2)", "int") - } - - test("array indexing assignment - LHS binding fix") { - // This tests the specific fix for "NewExpression in bindLHS" error - val setup = "var array = new Array[int](5)" - assertExprTypeWithSetup(setup, "array(0) = 42", "unit") - assertExprTypeWithSetup(setup, "array(1) = array(0) + 1", "unit") - } - } - - test("casting") { - test("primitive casts") { - // Basic numeric casting - assertExprTypeTest("42 as int", "int") - assertExprTypeTest("42 as bool", "bool") - assertExprTypeTest("42 as char", "char") - assertExprTypeTest("42 as string", "string") - - // Boolean casting - assertExprTypeTest("true as int", "int") - assertExprTypeTest("true as bool", "bool") - assertExprTypeTest("false as string", "string") - - // Character casting - assertExprTypeTest("'a' as int", "int") - assertExprTypeTest("'a' as char", "char") - assertExprTypeTest("'a' as string", "string") - - // String casting - assertExprTypeTest("\"hello\" as string", "string") - assertExprTypeTest("\"hello\" as any", "any") - } - - test("variable casts") { - val setup = - "val x = 42\nval flag = true\nval ch = 'a'\nval text = \"hello\"" - - assertExprTypeWithSetup(setup, "x as bool", "bool") - assertExprTypeWithSetup(setup, "x as char", "char") - assertExprTypeWithSetup(setup, "x as string", "string") - - assertExprTypeWithSetup(setup, "flag as int", "int") - assertExprTypeWithSetup(setup, "flag as string", "string") - - assertExprTypeWithSetup(setup, "ch as int", "int") - assertExprTypeWithSetup(setup, "ch as string", "string") - - assertExprTypeWithSetup(setup, "text as any", "any") - } - - test("cast to any type") { - assertExprTypeTest("42 as any", "any") - assertExprTypeTest("true as any", "any") - assertExprTypeTest("\"hello\" as any", "any") - assertExprTypeTest("'a' as any", "any") - assertExprTypeTest("() as any", "any") - } - - test("cast from any type") { - val setup = "val obj: any = 42" - - assertExprTypeWithSetup(setup, "obj as int", "int") - assertExprTypeWithSetup(setup, "obj as bool", "bool") - assertExprTypeWithSetup(setup, "obj as char", "char") - assertExprTypeWithSetup(setup, "obj as string", "string") - assertExprTypeWithSetup(setup, "obj as unit", "unit") - } - - test("expression casts") { - // Cast results of binary operations - assertExprTypeTest("(1 + 2) as bool", "bool") - assertExprTypeTest("(true && false) as int", "int") - assertExprTypeTest("(1 == 2) as string", "string") - - // Cast results of unary operations - assertExprTypeTest("(!true) as int", "int") - assertExprTypeTest("(-42) as bool", "bool") - } - - test("cast with parentheses") { - assertExprTypeTest("(42) as string", "string") - assertExprTypeTest("(true) as int", "int") - assertExprTypeTest("(\"hello\") as any", "any") - } - - test("chained operations with casts") { - val setup = "val x = 42" - - // Cast should have appropriate precedence - assertExprTypeWithSetup(setup, "x as bool == true", "bool") - assertExprTypeWithSetup(setup, "(x as bool) == true", "bool") - } - } - - test("string conversions") { - test("basic string conversions") { - // Convert literals to string - assertExprTypeTest("string(42)", "string") - assertExprTypeTest("string(0)", "string") - assertExprTypeTest("string(-123)", "string") - assertExprTypeTest("string(true)", "string") - assertExprTypeTest("string(false)", "string") - assertExprTypeTest("string('a')", "string") - assertExprTypeTest("string('z')", "string") - assertExprTypeTest("string(())", "string") - } - - test("string conversions with variables") { - val intSetup = "val num = 123" - assertExprTypeWithSetup(intSetup, "string(num)", "string") - - val boolSetup = "val flag = true" - assertExprTypeWithSetup(boolSetup, "string(flag)", "string") - - val charSetup = "val ch = 'A'" - assertExprTypeWithSetup(charSetup, "string(ch)", "string") - - val unitSetup = "val nothing = ()" - assertExprTypeWithSetup(unitSetup, "string(nothing)", "string") - } - - test("string conversions with expressions") { - // Arithmetic expressions - assertExprTypeTest("string(1 + 2)", "string") - assertExprTypeTest("string(10 - 5)", "string") - assertExprTypeTest("string(3 * 4)", "string") - assertExprTypeTest("string(15 / 3)", "string") - assertExprTypeTest("string(17 % 5)", "string") - - // Boolean expressions - assertExprTypeTest("string(true && false)", "string") - assertExprTypeTest("string(true || false)", "string") - assertExprTypeTest("string(!true)", "string") - assertExprTypeTest("string(5 > 3)", "string") - assertExprTypeTest("string(5 == 5)", "string") - assertExprTypeTest("string(5 != 3)", "string") - - // Unary expressions - assertExprTypeTest("string(-42)", "string") - assertExprTypeTest("string(+42)", "string") - } - - test("string conversions in complex expressions") { - val setup = "val x = 42\nval y = true" - - // String conversions in comparisons - assertExprTypeWithSetup(setup, "string(x) == \"42\"", "bool") - assertExprTypeWithSetup(setup, "string(y) == \"true\"", "bool") - assertExprTypeWithSetup(setup, "string(x) != \"0\"", "bool") - - // String conversions in arithmetic context - assertExprTypeWithSetup(setup, "string(x + 10)", "string") - assertExprTypeWithSetup(setup, "string(x * 2)", "string") - } - - test("string conversions with method calls") { - // Convert results of method calls to string - assertExprTypeTest("string(println(\"test\"))", "string") - assertExprTypeTest("string(print(42))", "string") - } - - test("string conversions with control flow") { - // String conversions with if expressions - assertExprTypeTest("string(if (true) 1 else 2)", "string") - assertExprTypeTest("string(if (false) true else false)", "string") - - // String conversions with block expressions - assertExprTypeTest("string({ 42 })", "string") - assertExprTypeTest("string({ true })", "string") - } - - test("nested string conversions") { - val setup = "val x = 42" - - // String conversion of string (should still work) - assertExprTypeTest("string(\"hello\")", "string") - assertExprTypeWithSetup(setup, "string(string(x))", "string") - - // String conversions in nested expressions - assertExprTypeWithSetup(setup, "string(string(x) == \"42\")", "string") - } - } - - test("other type conversions") { - test("int conversions") { - // Convert various types to int - assertExprTypeTest("int(42)", "int") - assertExprTypeTest("int(true)", "int") - assertExprTypeTest("int(false)", "int") - assertExprTypeTest("int('a')", "int") - - // Int conversions with variables and expressions - val setup = "val flag = true\nval ch = 'A'" - assertExprTypeWithSetup(setup, "int(flag)", "int") - assertExprTypeWithSetup(setup, "int(ch)", "int") - assertExprTypeTest("int(1 + 2)", "int") - } - - test("bool conversions") { - // Convert various types to bool - assertExprTypeTest("bool(true)", "bool") - assertExprTypeTest("bool(false)", "bool") - assertExprTypeTest("bool(42)", "bool") - assertExprTypeTest("bool(0)", "bool") - - // Bool conversions with variables and expressions - val setup = "val num = 123" - assertExprTypeWithSetup(setup, "bool(num)", "bool") - assertExprTypeTest("bool(5 > 3)", "bool") - } - - test("char conversions") { - // Convert various types to char - assertExprTypeTest("char('a')", "char") - assertExprTypeTest("char(65)", "char") - - // Char conversions with variables - val setup = "val ascii = 97" - assertExprTypeWithSetup(setup, "char(ascii)", "char") - } - } +import panther.* +import TestHelpers.* +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class TypeTests extends AnyFlatSpec with Matchers { + + "Type checker" should "handle primitive types" in { + assertExprTypeTest("12", "int") + assertExprTypeTest("0", "int") + assertExprTypeTest("true", "bool") + assertExprTypeTest("false", "bool") + assertExprTypeTest("\"hello\"", "string") + assertExprTypeTest("'a'", "char") + assertExprTypeTest("()", "unit") + } + + it should "handle binary expressions" in { + assertExprTypeTest("1 + 2", "int") + assertExprTypeTest("1 - 2", "int") + assertExprTypeTest("1 * 2", "int") + assertExprTypeTest("1 / 2", "int") + assertExprTypeTest("1 % 2", "int") + assertExprTypeTest("1 == 2", "bool") + assertExprTypeTest("1 != 2", "bool") + assertExprTypeTest("1 < 2", "bool") + assertExprTypeTest("1 <= 2", "bool") + assertExprTypeTest("1 > 2", "bool") + assertExprTypeTest("1 >= 2", "bool") + assertExprTypeTest("true && false", "bool") + assertExprTypeTest("true || false", "bool") + } + + it should "handle unary expressions" in { + assertExprTypeTest("-1", "int") + assertExprTypeTest("+1", "int") + // FIXME: assertExprType("~7", "int") + assertExprTypeTest("!true", "bool") + } + + it should "handle grouped expressions" in { + assertExprTypeTest("(12)", "int") + assertExprTypeTest("(true)", "bool") + } + + it should "handle int variables" in { + assertExprTypeWithSetup("val x = 12", "x", "int") + } + + it should "handle bool variables" in { + assertExprTypeWithSetup("val x = true", "x", "bool") + } + + it should "handle string variables" in { + assertExprTypeWithSetup("val x = \"hello\"", "x", "string") + } + + it should "handle char variables" in { + assertExprTypeWithSetup("val x = 'a'", "x", "char") + } + + it should "handle variable addition" in { + assertExprTypeWithSetup("val x = 12", "x + 12", "int") + } + + it should "handle variable equality" in { + assertExprTypeWithSetup("val x = 12", "12 == x", "bool") + } + + it should "handle variable assignment" in { + assertExprTypeWithSetup("var x = 12", "x = 10", "unit") + } + + it should "handle conversions to any" in { + assertAssignableTo("12", "any") + assertAssignableTo("true", "any") + assertAssignableTo("\"hello\"", "any") + assertAssignableTo("'a'", "any") + } + + it should "handle function calls" in { + assertExprTypeTest("println(12)", "unit") + assertExprTypeTest("print(12)", "unit") + assertExprTypeTest("print(\"hello\")", "unit") + assertExprTypeTest("print('a')", "unit") + assertExprTypeTest("print(true)", "unit") + assertExprTypeTest("print(12 + 12)", "unit") + assertExprTypeTest("print(12 == 12)", "unit") + assertExprTypeTest("print(12 < 12)", "unit") + assertExprTypeTest("print(true && false)", "unit") + assertExprTypeTest("print(true || false)", "unit") + } + + it should "handle casts" in { + assertExprTypeTest("'a' as char", "char") + assertExprTypeTest("12 as char", "char") + assertExprTypeTest("'a' as int", "int") + assertExprTypeTest("12 as int", "int") + assertExprTypeTest("'a' as string", "string") + assertExprTypeTest("12 as string", "string") + assertExprTypeTest("\"hello\" as string", "string") + assertExprTypeTest("true as string", "string") + } + + it should "handle is expressions" in { + assertExprTypeTest("12 is int", "bool") + assertExprTypeTest("'a' is char", "bool") + assertExprTypeTest("\"hello\" is string", "bool") + assertExprTypeTest("true is bool", "bool") + assertExprTypeTest("12 is bool", "bool") // should return false at runtime + } + + it should "handle block expressions" in { + assertExprTypeTest("{ 1 }", "int") + assertExprTypeTest("{ true }", "bool") + assertExprTypeTest("{ }", "unit") + assertExprTypeTest( + "{\n" + + " 1\n" + + " 2\n" + + "}", + "int" + ) + assertExprTypeTest( + "{\n" + + "true\n" + + "false\n" + + "}", + "bool" + ) + assertExprTypeTest( + "{\n" + + " 1\n" + + " true\n" + + "}", + "bool" + ) + assertExprTypeTest( + "{\n" + + " true\n" + + " 1\n" + + "}", + "int" + ) + } + + it should "handle if expressions" in { + assertExprTypeTest("if (true) 1 else 2", "int") + assertExprTypeTest("if (false) 1 else 2", "int") + assertExprTypeTest("if (true) true else false", "bool") + assertExprTypeTest("if (false) true else false", "bool") + assertExprTypeTest("if (false) true", "unit") + } + + it should "handle while expressions" in { + assertExprTypeTest("while (true) 1", "unit") + assertExprTypeTest("while (false) 1", "unit") + } + + it should "handle literal patterns" in { + assertExprTypeTest("1 match { case 1 => 2 }", "int") + assertExprTypeTest("1 match { case 2 => 3 }", "int") + assertExprTypeTest("true match { case true => false }", "bool") + assertExprTypeTest( + "\"hello\" match { case \"world\" => \"hi\" }", + "string" + ) + assertExprTypeTest("'a' match { case 'b' => 'c' }", "char") + } + + it should "handle wildcard patterns" in { + assertExprTypeTest("1 match { case _ => 2 }", "int") + assertExprTypeTest("true match { case _ => false }", "bool") + assertExprTypeTest("\"hello\" match { case _ => \"world\" }", "string") + } + + it should "handle multiple cases with same type" in { + assertExprTypeTest( + "1 match { case 1 => 10\n case 2 => 20 }", + "int" + ) + assertExprTypeTest( + "true match { case true => 1\n case false => 0 }", + "int" + ) + } + + it should "handle multiple cases with different types" in { + // When cases have different types, result should be 'any' (least upper bound) + assertExprTypeTest( + "1 match { case 1 => 42\n case 2 => \"hello\" }", + "int | string" + ) + assertExprTypeTest( + "1 match { case 1 => true\n case 2 => 123 }", + "bool | int" + ) + } + + it should "handle unit result cases" in { + assertExprTypeTest("1 match { case x: int => () }", "unit") + assertExprTypeTest("1 match { case 1 => println(\"test\") }", "unit") + } + + it should "handle nested matches" in { + assertExprTypeTest( + "1 match { case x: int => x match { case y: int => y * 2 } }", + "int" + ) + } + + it should "handle match with blocks" in { + assertExprTypeTest( + "1 match { case x: int => { val y = x + 1\n y * 2 } }", + "int" + ) + assertExprTypeTest( + "true match { case b: bool => { println(\"test\")\n b } }", + "bool" + ) + } + + it should "handle methods without return type" in { + val comp = mkCompilation("def foo() = 12") + val symbols = enumNonBuiltinSymbols(comp) + assertProgramSymbol(symbols) + val foo = assertSymbol(symbols, SymbolKind.Method, "foo") + assertSymbolType(comp, foo, "() -> int") + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } + + it should "handle methods with parameters" in { + val comp = mkCompilation("def foo(x: int, y: int) = 12") + val symbols = enumNonBuiltinSymbols(comp) + assertProgramSymbol(symbols) + val foo = assertSymbol(symbols, SymbolKind.Method, "foo") + assertSymbolType(comp, foo, "(x: int, y: int) -> int") + val x = assertSymbol(symbols, SymbolKind.Parameter, "x") + assertSymbolType(comp, x, "int") + val y = assertSymbol(symbols, SymbolKind.Parameter, "y") + assertSymbolType(comp, y, "int") + assertMainSymbol(symbols) + assertNoSymbols(symbols) + } + + it should "handle simple identity generic method" in { + val setup = "def identity[T](x: T): T = x" + + assertExprTypeWithSetup(setup, "identity(42)", "int") + assertExprTypeWithSetup(setup, "identity(true)", "bool") + assertExprTypeWithSetup(setup, "identity(\"hello\")", "string") + assertExprTypeWithSetup(setup, "identity('a')", "char") + } + + it should "handle generic method returning concrete type" in { + val setup = "def getValue[T](x: T): int = 42" + + // These should work because the return type is concrete + assertExprTypeWithSetup(setup, "getValue(42)", "int") + assertExprTypeWithSetup(setup, "getValue(\"hello\")", "int") + assertExprTypeWithSetup(setup, "getValue(true)", "int") + } + + it should "handle generic parameter with concrete return" in { + // Test methods that accept generic parameters but return concrete types + val setup = "def stringify[T](x: T): string = string(x)" + + assertExprTypeWithSetup(setup, "stringify(42)", "string") + assertExprTypeWithSetup(setup, "stringify(true)", "string") + assertExprTypeWithSetup(setup, "stringify('a')", "string") + } + + it should "handle generic container creation" in { + val containerSetup = "class Container[T](value: T)\n" + + "def wrap[T](x: T): Container[T] = new Container(x)" + + assertExprTypeWithSetup(containerSetup, "wrap(42)", "Container") + assertExprTypeWithSetup(containerSetup, "wrap(true)", "Container") + assertExprTypeWithSetup( + containerSetup, + "wrap(\"test\")", + "Container" + ) + } + + it should "handle enums without args" in { + val setup = "enum Foo {\n" + + " case Bar\n" + + " case Baz\n" + + "}" + assertExprTypeWithSetup(setup, "Foo.Bar", "Foo.Bar") + assertExprTypeWithSetup(setup, "Foo.Baz", "Foo.Baz") + } + + it should "handle enums with args" in { + val setup = "enum Foo {\n" + + " case Bar(x: int)\n" + + " case Baz(y: string)\n" + + "}" + assertExprTypeWithSetup(setup, "Foo.Bar(12)", "Foo.Bar") + assertExprTypeWithSetup(setup, "Foo.Baz(\"taco\")", "Foo.Baz") + assertExprTypeWithSetup(setup, "new Foo.Bar(12)", "Foo.Bar") + assertExprTypeWithSetup(setup, "new Foo.Baz(\"taco\")", "Foo.Baz") + } + + it should "check enum assignments" in { + val setup = "enum Foo {\n" + + " case Bar(x: int)\n" + + " case Baz(y: string)\n" + + "}" + assertAssignableToWithSetup(setup, "Foo.Bar(12)", "Foo") + assertAssignableToWithSetup(setup, "Foo.Baz(\"taco\")", "Foo") + } + + it should "handle enums with generic type" in { + val setup = "enum Option[T] {\n" + + " case Some(value: T)\n" + + " case None\n" + + "}" + assertAssignableToWithSetup(setup, "new Option.Some(12)", "Option[int]") + assertAssignableToWithSetup( + setup, + "new Option.Some(12)", + "Option.Some[int]" + ) + assertAssignableToWithSetup(setup, "Option.Some(12)", "Option[int]") + assertAssignableToWithSetup(setup, "Option.None", "Option[int]") + assertAssignableToWithSetup(setup, "Option.None", "Option[never]") + } + + it should "handle list examples" in { + val setup = "enum List[T] {\n" + + " case Cons(head: T, tail: List[T])\n" + + " case Nil\n" + + "}" + assertAssignableToWithSetup( + setup, + "new List.Cons(1, List.Nil)", + "List[int]" + ) + assertAssignableToWithSetup( + setup, + "List.Cons(1, List.Nil)", + "List[int]" + ) + assertAssignableToWithSetup( + setup, + "new List.Cons(\"hello\", List.Nil)", + "List[string]" + ) + assertAssignableToWithSetup( + setup, + "List.Cons(\"hello\", List.Nil)", + "List[string]" + ) + assertAssignableToWithSetup(setup, "List.Nil", "List[int]") + assertAssignableToWithSetup(setup, "List.Nil", "List[string]") + } + + it should "handle classes without args" in { + val setup = "class Foo()" + assertExprTypeWithSetup(setup, "new Foo()", "Foo") + } + + it should "handle classes with args" in { + val setup = "class Foo(x: int, y: string)" + assertExprTypeWithSetup(setup, "new Foo(12, \"taco\")", "Foo") + } + + it should "handle array length type" in { + val setup = "val array = new Array[int](0)" + assertExprTypeWithSetup(setup, "array.length", "int") + } + + it should "handle array apply with method call" in { + val setup = "val array = new Array[int](1)" + assertExprTypeWithSetup(setup, "array.apply(0)", "int") + } + + it should "handle array apply with indexer syntax for basic types" in { + val setup = "val intArray = new Array[int](1)" + assertExprTypeWithSetup(setup, "intArray(0)", "int") + + val boolSetup = "val boolArray = new Array[bool](1)" + assertExprTypeWithSetup(boolSetup, "boolArray(0)", "bool") + + val stringSetup = "val stringArray = new Array[string](1)" + assertExprTypeWithSetup(stringSetup, "stringArray(0)", "string") + + val charSetup = "val charArray = new Array[char](1)" + assertExprTypeWithSetup(charSetup, "charArray(0)", "char") + } + + it should "handle array indexing in expressions" in { + val setup = "val array = new Array[int](5)" + assertExprTypeWithSetup(setup, "array(0) + array(1)", "int") + assertExprTypeWithSetup(setup, "array(2) * 3", "int") + assertExprTypeWithSetup(setup, "array(0) == array(1)", "bool") + } + + it should "handle array indexing with computed indices" in { + val setup = "val array = new Array[int](10)\nval i = 5" + assertExprTypeWithSetup(setup, "array(i)", "int") + assertExprTypeWithSetup(setup, "array(1 + 2)", "int") + assertExprTypeWithSetup(setup, "array(i * 2)", "int") + } + + it should "handle array indexing assignment - LHS binding fix" in { + // This tests the specific fix for "NewExpression in bindLHS" error + val setup = "var array = new Array[int](5)" + assertExprTypeWithSetup(setup, "array(0) = 42", "unit") + assertExprTypeWithSetup(setup, "array(1) = array(0) + 1", "unit") + } + + it should "handle primitive casts" in { + // Basic numeric casting + assertExprTypeTest("42 as int", "int") + assertExprTypeTest("42 as bool", "bool") + assertExprTypeTest("42 as char", "char") + assertExprTypeTest("42 as string", "string") + + // Boolean casting + assertExprTypeTest("true as int", "int") + assertExprTypeTest("true as bool", "bool") + assertExprTypeTest("false as string", "string") + + // Character casting + assertExprTypeTest("'a' as int", "int") + assertExprTypeTest("'a' as char", "char") + assertExprTypeTest("'a' as string", "string") + + // String casting + assertExprTypeTest("\"hello\" as string", "string") + assertExprTypeTest("\"hello\" as any", "any") + } + + it should "handle variable casts" in { + val setup = + "val x = 42\nval flag = true\nval ch = 'a'\nval text = \"hello\"" + + assertExprTypeWithSetup(setup, "x as bool", "bool") + assertExprTypeWithSetup(setup, "x as char", "char") + assertExprTypeWithSetup(setup, "x as string", "string") + + assertExprTypeWithSetup(setup, "flag as int", "int") + assertExprTypeWithSetup(setup, "flag as string", "string") + + assertExprTypeWithSetup(setup, "ch as int", "int") + assertExprTypeWithSetup(setup, "ch as string", "string") + + assertExprTypeWithSetup(setup, "text as any", "any") + } + + it should "handle cast to any type" in { + assertExprTypeTest("42 as any", "any") + assertExprTypeTest("true as any", "any") + assertExprTypeTest("\"hello\" as any", "any") + assertExprTypeTest("'a' as any", "any") + assertExprTypeTest("() as any", "any") + } + + it should "handle cast from any type" in { + val setup = "val obj: any = 42" + + assertExprTypeWithSetup(setup, "obj as int", "int") + assertExprTypeWithSetup(setup, "obj as bool", "bool") + assertExprTypeWithSetup(setup, "obj as char", "char") + assertExprTypeWithSetup(setup, "obj as string", "string") + assertExprTypeWithSetup(setup, "obj as unit", "unit") + } + + it should "handle expression casts" in { + // Cast results of binary operations + assertExprTypeTest("(1 + 2) as bool", "bool") + assertExprTypeTest("(true && false) as int", "int") + assertExprTypeTest("(1 == 2) as string", "string") + + // Cast results of unary operations + assertExprTypeTest("(!true) as int", "int") + assertExprTypeTest("(-42) as bool", "bool") + } + + it should "handle cast with parentheses" in { + assertExprTypeTest("(42) as string", "string") + assertExprTypeTest("(true) as int", "int") + assertExprTypeTest("(\"hello\") as any", "any") + } + + it should "handle chained operations with casts" in { + val setup = "val x = 42" + + // Cast should have appropriate precedence + assertExprTypeWithSetup(setup, "x as bool == true", "bool") + assertExprTypeWithSetup(setup, "(x as bool) == true", "bool") + } + + it should "handle basic string conversions" in { + // Convert literals to string + assertExprTypeTest("string(42)", "string") + assertExprTypeTest("string(0)", "string") + assertExprTypeTest("string(-123)", "string") + assertExprTypeTest("string(true)", "string") + assertExprTypeTest("string(false)", "string") + assertExprTypeTest("string('a')", "string") + assertExprTypeTest("string('z')", "string") + assertExprTypeTest("string(())", "string") + } + + it should "handle string conversions with variables" in { + val intSetup = "val num = 123" + assertExprTypeWithSetup(intSetup, "string(num)", "string") + + val boolSetup = "val flag = true" + assertExprTypeWithSetup(boolSetup, "string(flag)", "string") + + val charSetup = "val ch = 'A'" + assertExprTypeWithSetup(charSetup, "string(ch)", "string") + + val unitSetup = "val nothing = ()" + assertExprTypeWithSetup(unitSetup, "string(nothing)", "string") + } + + it should "handle string conversions with expressions" in { + // Arithmetic expressions + assertExprTypeTest("string(1 + 2)", "string") + assertExprTypeTest("string(10 - 5)", "string") + assertExprTypeTest("string(3 * 4)", "string") + assertExprTypeTest("string(15 / 3)", "string") + assertExprTypeTest("string(17 % 5)", "string") + + // Boolean expressions + assertExprTypeTest("string(true && false)", "string") + assertExprTypeTest("string(true || false)", "string") + assertExprTypeTest("string(!true)", "string") + assertExprTypeTest("string(5 > 3)", "string") + assertExprTypeTest("string(5 == 5)", "string") + assertExprTypeTest("string(5 != 3)", "string") + + // Unary expressions + assertExprTypeTest("string(-42)", "string") + assertExprTypeTest("string(+42)", "string") + } + + it should "handle string conversions in complex expressions" in { + val setup = "val x = 42\nval y = true" + + // String conversions in comparisons + assertExprTypeWithSetup(setup, "string(x) == \"42\"", "bool") + assertExprTypeWithSetup(setup, "string(y) == \"true\"", "bool") + assertExprTypeWithSetup(setup, "string(x) != \"0\"", "bool") + + // String conversions in arithmetic context + assertExprTypeWithSetup(setup, "string(x + 10)", "string") + assertExprTypeWithSetup(setup, "string(x * 2)", "string") + } + + it should "handle string conversions with method calls" in { + // Convert results of method calls to string + assertExprTypeTest("string(println(\"test\"))", "string") + assertExprTypeTest("string(print(42))", "string") + } + + it should "handle string conversions with control flow" in { + // String conversions with if expressions + assertExprTypeTest("string(if (true) 1 else 2)", "string") + assertExprTypeTest("string(if (false) true else false)", "string") + + // String conversions with block expressions + assertExprTypeTest("string({ 42 })", "string") + assertExprTypeTest("string({ true })", "string") + } + + it should "handle nested string conversions" in { + val setup = "val x = 42" + + // String conversion of string (should still work) + assertExprTypeTest("string(\"hello\")", "string") + assertExprTypeWithSetup(setup, "string(string(x))", "string") + + // String conversions in nested expressions + assertExprTypeWithSetup(setup, "string(string(x) == \"42\")", "string") + } + + it should "handle int conversions" in { + // Convert various types to int + assertExprTypeTest("int(42)", "int") + assertExprTypeTest("int(true)", "int") + assertExprTypeTest("int(false)", "int") + assertExprTypeTest("int('a')", "int") + + // Int conversions with variables and expressions + val setup = "val flag = true\nval ch = 'A'" + assertExprTypeWithSetup(setup, "int(flag)", "int") + assertExprTypeWithSetup(setup, "int(ch)", "int") + assertExprTypeTest("int(1 + 2)", "int") + } + + it should "handle bool conversions" in { + // Convert various types to bool + assertExprTypeTest("bool(true)", "bool") + assertExprTypeTest("bool(false)", "bool") + assertExprTypeTest("bool(42)", "bool") + assertExprTypeTest("bool(0)", "bool") + + // Bool conversions with variables and expressions + val setup = "val num = 123" + assertExprTypeWithSetup(setup, "bool(num)", "bool") + assertExprTypeTest("bool(5 > 3)", "bool") + } + + it should "handle char conversions" in { + // Convert various types to char + assertExprTypeTest("char('a')", "char") + assertExprTypeTest("char(65)", "char") + + // Char conversions with variables + val setup = "val ascii = 97" + assertExprTypeWithSetup(setup, "char(ascii)", "char") } } diff --git a/test/src/test/scala/VmTests.scala b/test/src/test/scala/VmTests.scala index 82992c9..643b823 100644 --- a/test/src/test/scala/VmTests.scala +++ b/test/src/test/scala/VmTests.scala @@ -1,252 +1,253 @@ import TestHelpers._ -import utest._ - -object VmTests extends TestSuite { - val tests = Tests { - test("constants") { - assertExecValueInt("12", 12) - assertExecValueInt("0", 0) - assertExecValueInt("-5", -5) - assertExecValueBool("true", true) - assertExecValueBool("false", false) - assertExecValueString("\"hello\"", "hello") - } - - test("unary") { - assertExecValueInt("-12", -12) - assertExecValueInt("+12", 12) - assertExecValueBool("!true", false) - assertExecValueBool("!false", true) - } - - test("binary") { - assertExecValueInt("1 + 2", 3) - assertExecValueInt("5 - 3", 2) - assertExecValueInt("4 * 3", 12) - assertExecValueInt("15 / 3", 5) - assertExecValueInt("17 % 5", 2) - assertExecValueInt("17 & 5", 1) - assertExecValueInt("17 | 5", 21) - - assertExecValueBool("5 == 5", true) - assertExecValueBool("5 == 3", false) - assertExecValueBool("5 != 3", true) - assertExecValueBool("5 != 5", false) - assertExecValueBool("5 < 10", true) - assertExecValueBool("10 < 5", false) - assertExecValueBool("5 <= 5", true) - assertExecValueBool("5 <= 3", false) - assertExecValueBool("10 > 5", true) - assertExecValueBool("5 > 10", false) - assertExecValueBool("5 >= 5", true) - assertExecValueBool("3 >= 5", false) - - assertExecValueBool("true && true", true) - assertExecValueBool("true && false", false) - assertExecValueBool("false && true", false) - assertExecValueBool("false && false", false) - assertExecValueBool("true || true", true) - assertExecValueBool("true || false", true) - assertExecValueBool("false || true", true) - assertExecValueBool("false || false", false) - } - - test("binary precedence") { - assertExecValueInt("1 + 2 * 3", 7) // 1 + (2 * 3) - assertExecValueInt("2 * 3 + 1", 7) // (2 * 3) + 1 - assertExecValueInt("10 - 4 / 2", 8) // 10 - (4 / 2) - } - - test("unary precedence") { - assertExecValueInt("-2 + 3", 1) // (-2) + 3 - assertExecValueInt("-(2 + 3)", -5) // -(2 + 3) - } - - test("locals") { - assertExecValueIntWithSetup("val x = 12", "x", 12) - assertExecValueIntWithSetup("val x = 12", "x + 7", 19) - assertExecValueIntWithSetup("val x = 12", "7 + x", 19) - assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "x", 12) - assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "y", 13) - } - - test("if expressions") { - assertExecValueInt("if (true) 1 else 2", 1) - assertExecValueInt("if (false) 1 else 2", 2) - assertExecValueInt("if (5 > 3) 10 else 20", 10) - assertExecValueInt("if (3 > 5) 10 else 20", 20) - } - - test("calls") { - assertExecValueIntWithSetup( - "def noargs(): int = 7", - "noargs()", - 7 - ) - assertExecValueIntWithSetup( - "def onearg(x: int): int = x + 1", - "onearg(12)", - 13 - ) - assertExecValueIntWithSetup( - "def twoargs(x: int, y: int): int = x + y", - "twoargs(12, 13)", - 25 - ) - assertExecValueIntWithSetup( - "def threeargs(x: int, y: int, z: int): int = x + y + z", - "threeargs(12, 13, 14)", - 39 - ) - } - - test("object methods") { - val setup = "object TestObject {\n" + - " def testMethod(x: int): int = x + 1\n" + - " def testMethod2(x: int, y: int): int = x + y\n" + - " def noArgs(): int = 42\n" + - " def threeArgs(a: int, b: int, c: int): int = a * b + c\n" + - " def multiply(x: int, y: int): int = x * y\n" + - " def subtract(x: int, y: int): int = x - y\n" + - " def negate(x: int): int = -x\n" + - "}\n" - - assertExecValueIntWithSetup(setup, "TestObject.testMethod(0)", 1) - assertExecValueIntWithSetup(setup, "TestObject.testMethod(11)", 12) - assertExecValueIntWithSetup(setup, "TestObject.testMethod(12)", 13) - assertExecValueIntWithSetup(setup, "TestObject.testMethod2(11, 12)", 23) - assertExecValueIntWithSetup(setup, "TestObject.testMethod2(12, 13)", 25) - - // No arguments - assertExecValueIntWithSetup(setup, "TestObject.noArgs()", 42) - - // Three arguments - assertExecValueIntWithSetup( - setup, - "TestObject.threeArgs(2, 3, 4)", - 10 - ) // 2 * 3 + 4 - assertExecValueIntWithSetup( - setup, - "TestObject.threeArgs(5, 6, 7)", - 37 - ) // 5 * 6 + 7 - assertExecValueIntWithSetup( - setup, - "TestObject.threeArgs(0, 10, 5)", - 5 - ) // 0 * 10 + 5 - - // More arithmetic operations - assertExecValueIntWithSetup(setup, "TestObject.multiply(7, 8)", 56) - assertExecValueIntWithSetup(setup, "TestObject.multiply(-3, 4)", -12) - assertExecValueIntWithSetup(setup, "TestObject.subtract(10, 3)", 7) - assertExecValueIntWithSetup(setup, "TestObject.subtract(5, 8)", -3) - assertExecValueIntWithSetup(setup, "TestObject.negate(42)", -42) - assertExecValueIntWithSetup(setup, "TestObject.negate(-15)", 15) - - // Nested calls - assertExecValueIntWithSetup( - setup, - "TestObject.testMethod(TestObject.noArgs())", - 43 - ) // testMethod(42) = 43 - assertExecValueIntWithSetup( - setup, - "TestObject.multiply(TestObject.testMethod(2), 5)", - 15 - ) // multiply(3, 5) = 15 - } - - test("object fields") { - val setup = "object Taco {\n" + - " val field1 = 12\n" + - " val field2 = 13\n" + - "}\n" + - "object Calculator {\n" + - " val zero = 0\n" + - " val one = 1\n" + - " val negativeValue = -42\n" + - " val largeValue = 9999\n" + - "}\n" + - "object MathConstants {\n" + - " val pi = 3\n" + // Simplified for int - " val e = 2\n" + // Simplified for int - "}\n" - - assertExecValueIntWithSetup(setup, "Taco.field1", 12) - assertExecValueIntWithSetup(setup, "Taco.field2", 13) - - // Calculator object tests - assertExecValueIntWithSetup(setup, "Calculator.zero", 0) - assertExecValueIntWithSetup(setup, "Calculator.one", 1) - assertExecValueIntWithSetup(setup, "Calculator.negativeValue", -42) - assertExecValueIntWithSetup(setup, "Calculator.largeValue", 9999) - - // MathConstants tests - assertExecValueIntWithSetup(setup, "MathConstants.pi", 3) - assertExecValueIntWithSetup(setup, "MathConstants.e", 2) - - // Field access in expressions - assertExecValueIntWithSetup( - setup, - "Taco.field1 + Taco.field2", - 25 - ) // 12 + 13 - assertExecValueIntWithSetup( - setup, - "Calculator.zero + Calculator.one", - 1 - ) // 0 + 1 - assertExecValueIntWithSetup( - setup, - "MathConstants.pi * MathConstants.e", - 6 - ) // 3 * 2 - assertExecValueIntWithSetup( - setup, - "Taco.field1 * Calculator.one", - 12 - ) // 12 * 1 - assertExecValueIntWithSetup( - setup, - "Calculator.largeValue - Taco.field1", - 9987 - ) // 9999 - 12 - - // More complex expressions - assertExecValueIntWithSetup( - setup, - "Taco.field1 + Taco.field2 * Calculator.one", - 25 - ) // 12 + 13 * 1 - assertExecValueIntWithSetup( - setup, - "(Taco.field1 + Taco.field2) * MathConstants.e", - 50 - ) // (12 + 13) * 2 - assertExecValueIntWithSetup( - setup, - "Calculator.largeValue + Calculator.negativeValue", - 9957 - ) // 9999 + (-42) - - // Test with unary operations - assertExecValueIntWithSetup(setup, "-Taco.field1", -12) - assertExecValueIntWithSetup(setup, "-Calculator.negativeValue", 42) - } - - test("classes without args") { - assertExecValueIntWithSetup( - "class Foo() {\n" + - " def bar() = 12\n" + - "}", - "new Foo().bar()", - 12 - ) - } - -// test("classes with args") { +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class VmTests extends AnyFlatSpec with Matchers { + + "VM" should "execute constants" in { + assertExecValueInt("12", 12) + assertExecValueInt("0", 0) + assertExecValueInt("-5", -5) + assertExecValueBool("true", true) + assertExecValueBool("false", false) + assertExecValueString("\"hello\"", "hello") + } + + it should "execute unary operations" in { + assertExecValueInt("-12", -12) + assertExecValueInt("+12", 12) + assertExecValueBool("!true", false) + assertExecValueBool("!false", true) + } + + it should "execute binary operations" in { + assertExecValueInt("1 + 2", 3) + assertExecValueInt("5 - 3", 2) + assertExecValueInt("4 * 3", 12) + assertExecValueInt("15 / 3", 5) + assertExecValueInt("17 % 5", 2) + assertExecValueInt("17 & 5", 1) + assertExecValueInt("17 | 5", 21) + + assertExecValueBool("5 == 5", true) + assertExecValueBool("5 == 3", false) + assertExecValueBool("5 != 3", true) + assertExecValueBool("5 != 5", false) + assertExecValueBool("5 < 10", true) + assertExecValueBool("10 < 5", false) + assertExecValueBool("5 <= 5", true) + assertExecValueBool("5 <= 3", false) + assertExecValueBool("10 > 5", true) + assertExecValueBool("5 > 10", false) + assertExecValueBool("5 >= 5", true) + assertExecValueBool("3 >= 5", false) + + assertExecValueBool("true && true", true) + assertExecValueBool("true && false", false) + assertExecValueBool("false && true", false) + assertExecValueBool("false && false", false) + assertExecValueBool("true || true", true) + assertExecValueBool("true || false", true) + assertExecValueBool("false || true", true) + assertExecValueBool("false || false", false) + } + + it should "handle binary precedence correctly" in { + assertExecValueInt("1 + 2 * 3", 7) // 1 + (2 * 3) + assertExecValueInt("2 * 3 + 1", 7) // (2 * 3) + 1 + assertExecValueInt("10 - 4 / 2", 8) // 10 - (4 / 2) + } + + it should "handle unary precedence correctly" in { + assertExecValueInt("-2 + 3", 1) // (-2) + 3 + assertExecValueInt("-(2 + 3)", -5) // -(2 + 3) + } + + it should "execute local variable declarations and usage" in { + assertExecValueIntWithSetup("val x = 12", "x", 12) + assertExecValueIntWithSetup("val x = 12", "x + 7", 19) + assertExecValueIntWithSetup("val x = 12", "7 + x", 19) + assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "x", 12) + assertExecValueIntWithSetup("val x = 12\nval y = x + 1", "y", 13) + } + + it should "execute if expressions" in { + assertExecValueInt("if (true) 1 else 2", 1) + assertExecValueInt("if (false) 1 else 2", 2) + assertExecValueInt("if (5 > 3) 10 else 20", 10) + assertExecValueInt("if (3 > 5) 10 else 20", 20) + } + + it should "execute function calls" in { + assertExecValueIntWithSetup( + "def noargs(): int = 7", + "noargs()", + 7 + ) + assertExecValueIntWithSetup( + "def onearg(x: int): int = x + 1", + "onearg(12)", + 13 + ) + assertExecValueIntWithSetup( + "def twoargs(x: int, y: int): int = x + y", + "twoargs(12, 13)", + 25 + ) + assertExecValueIntWithSetup( + "def threeargs(x: int, y: int, z: int): int = x + y + z", + "threeargs(12, 13, 14)", + 39 + ) + } + + it should "execute object methods" in { + val setup = "object TestObject {\n" + + " def testMethod(x: int): int = x + 1\n" + + " def testMethod2(x: int, y: int): int = x + y\n" + + " def noArgs(): int = 42\n" + + " def threeArgs(a: int, b: int, c: int): int = a * b + c\n" + + " def multiply(x: int, y: int): int = x * y\n" + + " def subtract(x: int, y: int): int = x - y\n" + + " def negate(x: int): int = -x\n" + + "}\n" + + assertExecValueIntWithSetup(setup, "TestObject.testMethod(0)", 1) + assertExecValueIntWithSetup(setup, "TestObject.testMethod(11)", 12) + assertExecValueIntWithSetup(setup, "TestObject.testMethod(12)", 13) + assertExecValueIntWithSetup(setup, "TestObject.testMethod2(11, 12)", 23) + assertExecValueIntWithSetup(setup, "TestObject.testMethod2(12, 13)", 25) + + // No arguments + assertExecValueIntWithSetup(setup, "TestObject.noArgs()", 42) + + // Three arguments + assertExecValueIntWithSetup( + setup, + "TestObject.threeArgs(2, 3, 4)", + 10 + ) // 2 * 3 + 4 + assertExecValueIntWithSetup( + setup, + "TestObject.threeArgs(5, 6, 7)", + 37 + ) // 5 * 6 + 7 + assertExecValueIntWithSetup( + setup, + "TestObject.threeArgs(0, 10, 5)", + 5 + ) // 0 * 10 + 5 + + // More arithmetic operations + assertExecValueIntWithSetup(setup, "TestObject.multiply(7, 8)", 56) + assertExecValueIntWithSetup(setup, "TestObject.multiply(-3, 4)", -12) + assertExecValueIntWithSetup(setup, "TestObject.subtract(10, 3)", 7) + assertExecValueIntWithSetup(setup, "TestObject.subtract(5, 8)", -3) + assertExecValueIntWithSetup(setup, "TestObject.negate(42)", -42) + assertExecValueIntWithSetup(setup, "TestObject.negate(-15)", 15) + + // Nested calls + assertExecValueIntWithSetup( + setup, + "TestObject.testMethod(TestObject.noArgs())", + 43 + ) // testMethod(42) = 43 + assertExecValueIntWithSetup( + setup, + "TestObject.multiply(TestObject.testMethod(2), 5)", + 15 + ) // multiply(3, 5) = 15 + } + + it should "access object fields" in { + val setup = "object Taco {\n" + + " val field1 = 12\n" + + " val field2 = 13\n" + + "}\n" + + "object Calculator {\n" + + " val zero = 0\n" + + " val one = 1\n" + + " val negativeValue = -42\n" + + " val largeValue = 9999\n" + + "}\n" + + "object MathConstants {\n" + + " val pi = 3\n" + // Simplified for int + " val e = 2\n" + // Simplified for int + "}\n" + + assertExecValueIntWithSetup(setup, "Taco.field1", 12) + assertExecValueIntWithSetup(setup, "Taco.field2", 13) + + // Calculator object tests + assertExecValueIntWithSetup(setup, "Calculator.zero", 0) + assertExecValueIntWithSetup(setup, "Calculator.one", 1) + assertExecValueIntWithSetup(setup, "Calculator.negativeValue", -42) + assertExecValueIntWithSetup(setup, "Calculator.largeValue", 9999) + + // MathConstants tests + assertExecValueIntWithSetup(setup, "MathConstants.pi", 3) + assertExecValueIntWithSetup(setup, "MathConstants.e", 2) + + // Field access in expressions + assertExecValueIntWithSetup( + setup, + "Taco.field1 + Taco.field2", + 25 + ) // 12 + 13 + assertExecValueIntWithSetup( + setup, + "Calculator.zero + Calculator.one", + 1 + ) // 0 + 1 + assertExecValueIntWithSetup( + setup, + "MathConstants.pi * MathConstants.e", + 6 + ) // 3 * 2 + assertExecValueIntWithSetup( + setup, + "Taco.field1 * Calculator.one", + 12 + ) // 12 * 1 + assertExecValueIntWithSetup( + setup, + "Calculator.largeValue - Taco.field1", + 9987 + ) // 9999 - 12 + + // More complex expressions + assertExecValueIntWithSetup( + setup, + "Taco.field1 + Taco.field2 * Calculator.one", + 25 + ) // 12 + 13 * 1 + assertExecValueIntWithSetup( + setup, + "(Taco.field1 + Taco.field2) * MathConstants.e", + 50 + ) // (12 + 13) * 2 + assertExecValueIntWithSetup( + setup, + "Calculator.largeValue + Calculator.negativeValue", + 9957 + ) // 9999 + (-42) + + // Test with unary operations + assertExecValueIntWithSetup(setup, "-Taco.field1", -12) + assertExecValueIntWithSetup(setup, "-Calculator.negativeValue", 42) + } + + it should "execute classes without args" in { + assertExecValueIntWithSetup( + "class Foo() {\n" + + " def bar() = 12\n" + + "}", + "new Foo().bar()", + 12 + ) + } + +// it should "execute classes with args" in { // assertExecValueIntWithSetup( // "class Foo(x: int, y: int) {\n" + // " def add() = x + y\n" + @@ -256,7 +257,7 @@ object VmTests extends TestSuite { // ) // } // -// test("class fields via constructor") { +// it should "access class fields via constructor" in { // assertExecValueIntWithSetup( // "class Foo(x: int, y: int)", // "new Foo(1, 2).x + new Foo(3,5).y", @@ -264,94 +265,94 @@ object VmTests extends TestSuite { // ) // } - test("class fields via field declaration") { - assertExecValueIntWithSetup( - "class Foo() {\n" + - " var x = 12\n" + - " var y = 13\n" + - "}", - "new Foo().x + new Foo().y", - 25 - ) - } - - test("pattern matching: integer cases") { - assertExecValueIntWithSetup( - "val x = 1", - "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", - 10 - ) - assertExecValueIntWithSetup( - "val x = 2", - "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", - 20 - ) - assertExecValueIntWithSetup( - "val x = 5", - "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", - 0 - ) - } - - test("pattern matching: wildcard only") { - assertExecValueIntWithSetup( - "val x = 42", - "x match {\n case _ => 99\n}", - 99 - ) - } - - test("is expression basic functionality") { - // Test that is expressions execute properly and return correct boolean values - assertExecValueBool("12 is int", true) - assertExecValueBool("12 is bool", false) - assertExecValueBool("true is bool", true) - assertExecValueBool("true is int", false) - assertExecValueBool("\"hello\" is string", true) - assertExecValueBool("\"hello\" is int", false) - - // Test with variables - assertExecValueIntWithSetup("val x = 12", "if (x is int) 1 else 0", 1) - assertExecValueIntWithSetup("val x = 12", "if (x is bool) 1 else 0", 0) - assertExecValueIntWithSetup("val y = true", "if (y is bool) 1 else 0", 1) - assertExecValueIntWithSetup( - "val y = true", - "if (y is string) 1 else 0", - 0 - ) - } - - test("cast expressions: identity casts") { - // Test identity casts (casting to the same type) - assertExecValueInt("12 as int", 12) - assertExecValueBool("true as bool", true) - assertExecValueBool("false as bool", false) - assertExecValueString("\"hello\" as string", "hello") - - // Test with variables - assertExecValueIntWithSetup("val x = 42", "x as int", 42) - assertExecValueBoolWithSetup("val flag = true", "flag as bool", true) - assertExecValueStringWithSetup( - "val text = \"world\"", - "text as string", - "world" - ) - } - - test("cast expressions: valid conversions") { - // Test casts that should succeed at runtime - // Skip char literals for now since emitCharacterLiteral is not implemented - assertExecValueString("42 as string", "42") // int to string - assertExecValueString("true as string", "true") // bool to string - assertExecValueString("false as string", "false") // bool to string - - // Test with expressions - assertExecValueInt("(1 + 2) as int", 3) - assertExecValueString("(5 * 6) as string", "30") - assertExecValueBool("(10 > 5) as bool", true) - } - -// test("cast expressions: cast to any type") { + it should "access class fields via field declaration" in { + assertExecValueIntWithSetup( + "class Foo() {\n" + + " var x = 12\n" + + " var y = 13\n" + + "}", + "new Foo().x + new Foo().y", + 25 + ) + } + + it should "execute pattern matching with integer cases" in { + assertExecValueIntWithSetup( + "val x = 1", + "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", + 10 + ) + assertExecValueIntWithSetup( + "val x = 2", + "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", + 20 + ) + assertExecValueIntWithSetup( + "val x = 5", + "x match {\n case 1 => 10\n case 2 => 20\n case _ => 0\n}", + 0 + ) + } + + it should "execute pattern matching with wildcard only" in { + assertExecValueIntWithSetup( + "val x = 42", + "x match {\n case _ => 99\n}", + 99 + ) + } + + it should "execute is expression basic functionality" in { + // Test that is expressions execute properly and return correct boolean values + assertExecValueBool("12 is int", true) + assertExecValueBool("12 is bool", false) + assertExecValueBool("true is bool", true) + assertExecValueBool("true is int", false) + assertExecValueBool("\"hello\" is string", true) + assertExecValueBool("\"hello\" is int", false) + + // Test with variables + assertExecValueIntWithSetup("val x = 12", "if (x is int) 1 else 0", 1) + assertExecValueIntWithSetup("val x = 12", "if (x is bool) 1 else 0", 0) + assertExecValueIntWithSetup("val y = true", "if (y is bool) 1 else 0", 1) + assertExecValueIntWithSetup( + "val y = true", + "if (y is string) 1 else 0", + 0 + ) + } + + it should "execute cast expressions - identity casts" in { + // Test identity casts (casting to the same type) + assertExecValueInt("12 as int", 12) + assertExecValueBool("true as bool", true) + assertExecValueBool("false as bool", false) + assertExecValueString("\"hello\" as string", "hello") + + // Test with variables + assertExecValueIntWithSetup("val x = 42", "x as int", 42) + assertExecValueBoolWithSetup("val flag = true", "flag as bool", true) + assertExecValueStringWithSetup( + "val text = \"world\"", + "text as string", + "world" + ) + } + + it should "execute cast expressions - valid conversions" in { + // Test casts that should succeed at runtime + // Skip char literals for now since emitCharacterLiteral is not implemented + assertExecValueString("42 as string", "42") // int to string + assertExecValueString("true as string", "true") // bool to string + assertExecValueString("false as string", "false") // bool to string + + // Test with expressions + assertExecValueInt("(1 + 2) as int", 3) + assertExecValueString("(5 * 6) as string", "30") + assertExecValueBool("(10 > 5) as bool", true) + } + +// it should "execute cast expressions - cast to any type" in { // // Test casting various types to 'any' type // // Note: The actual value should remain the same, just the type changes // assertExecValueInt("42 as any as int", 42) @@ -363,7 +364,7 @@ object VmTests extends TestSuite { // assertExecValueBoolWithSetup("val flag = false", "flag as any as bool", false) // } // -// test("cast expressions: cast from any type") { +// it should "execute cast expressions - cast from any type" in { // // Test casting from 'any' type to specific types // val anySetup = "val obj: any = 42" // assertExecValueIntWithSetup(anySetup, "obj as int", 42) @@ -375,7 +376,7 @@ object VmTests extends TestSuite { // assertExecValueStringWithSetup(anyStringSetup, "obj as string", "hello") // } // -// test("cast expressions: chained casts") { +// it should "execute cast expressions - chained casts" in { // // Test multiple casts in sequence // assertExecValueInt("42 as any as int", 42) // assertExecValueString("42 as string as any as string", "42") @@ -386,295 +387,294 @@ object VmTests extends TestSuite { // assertExecValueString("(\"hel\" + \"lo\") as any as string", "hello") // } - test("cast expressions: cast with operators") { - // Test that cast has correct precedence with other operators - assertExecValueBool("42 as int == 42", true) - assertExecValueBool("42 as string == \"42\"", true) - assertExecValueBool("true as bool && false", false) - assertExecValueBool("false as bool || true", true) - - // Test cast in arithmetic expressions - assertExecValueInt("(40 + 2) as int", 42) - assertExecValueInt("40 + 2 as int", 42) // Should be: 40 + (2 as int) - } - - test("cast expressions: cast in control flow") { - // Test cast expressions in if conditions - assertExecValueInt("if (42 as int == 42) 1 else 0", 1) - assertExecValueInt("if (true as bool) 100 else 200", 100) - assertExecValueInt("if (false as bool) 100 else 200", 200) - - // Test cast in if branches - assertExecValueInt("if (true) 42 as int else 0", 42) - assertExecValueString("if (false) \"no\" else \"yes\" as string", "yes") - - // Test with variables - assertExecValueIntWithSetup( - "val x = 50", - "if (x as int > 40) 1 else 0", - 1 - ) - } - - test("array indexing - basic access") { - // Test basic array indexing with literal indices - val setup = "val array = new Array[int](3)" - // Note: These tests assume array elements are initialized to 0 - // If that's not the case, we may need to modify the setup - assertExecValueIntWithSetup(setup, "array(0)", 0) - assertExecValueIntWithSetup(setup, "array(1)", 0) - assertExecValueIntWithSetup(setup, "array(2)", 0) - } - - test("array indexing - with assignment") { - // Test the specific "NewExpression in bindLHS" fix - val setup = "var array = new Array[int](5)" - // First test assignment returns unit - assertExecValueIntWithSetup( - setup + "\narray(0) = 42", - "0", - 0 - ) // dummy assertion to ensure compilation - - // Test assignment followed by access - val assignAndRead = setup + "\narray(0) = 42\narray(1) = 13" - assertExecValueIntWithSetup(assignAndRead, "array(0)", 42) - assertExecValueIntWithSetup(assignAndRead, "array(1)", 13) - } - - test("array indexing - computed indices") { - val setup = "var array = new Array[int](10)\nval i = 5\narray(i) = 99" - assertExecValueIntWithSetup(setup, "array(i)", 99) - assertExecValueIntWithSetup(setup, "array(5)", 99) - - // Test with arithmetic expressions as indices - val complexSetup = setup + "\narray(2 * 3) = 77\narray(1 + 4) = 55" - assertExecValueIntWithSetup(complexSetup, "array(6)", 77) - assertExecValueIntWithSetup( - complexSetup, - "array(5)", - 55 - ) // This overwrites the previous value at index 5 - } - - test("array indexing - in expressions") { - val setup = - "var array = new Array[int](5)\narray(0) = 10\narray(1) = 20\narray(2) = 30" - - // Test array indexing in arithmetic expressions - assertExecValueIntWithSetup(setup, "array(0) + array(1)", 30) - assertExecValueIntWithSetup(setup, "array(2) - array(0)", 20) - assertExecValueIntWithSetup(setup, "array(1) * 2", 40) - - // Test array indexing in boolean expressions - assertExecValueBoolWithSetup(setup, "array(0) == 10", true) - assertExecValueBoolWithSetup(setup, "array(1) > array(0)", true) - assertExecValueBoolWithSetup(setup, "array(2) < array(1)", false) - } - - test("array indexing - different types") { - // Test array indexing with different element types - val boolSetup = - "var boolArray = new Array[bool](3)\nboolArray(0) = true\nboolArray(1) = false" - assertExecValueBoolWithSetup(boolSetup, "boolArray(0)", true) - assertExecValueBoolWithSetup(boolSetup, "boolArray(1)", false) - - val stringSetup = - "var stringArray = new Array[string](2)\nstringArray(0) = \"hello\"\nstringArray(1) = \"world\"" - assertExecValueStringWithSetup(stringSetup, "stringArray(0)", "hello") - assertExecValueStringWithSetup(stringSetup, "stringArray(1)", "world") - } - - test("string conversions - basic literals") { - // Test string conversion of basic literals - assertExecValueString("string(42)", "42") - assertExecValueString("string(0)", "0") - assertExecValueString("string(-123)", "-123") - assertExecValueString("string(true)", "true") - assertExecValueString("string(false)", "false") - - // String conversion of string (identity) - assertExecValueString("string(\"hello\")", "hello") - assertExecValueString("string(\"\")", "") - } - - test("string conversions - with variables") { - // Test string conversion of variables - assertExecValueStringWithSetup("val num = 123", "string(num)", "123") - assertExecValueStringWithSetup("val flag = true", "string(flag)", "true") - assertExecValueStringWithSetup( - "val flag2 = false", - "string(flag2)", - "false" - ) - assertExecValueStringWithSetup( - "val text = \"world\"", - "string(text)", - "world" - ) - - // Negative numbers - assertExecValueStringWithSetup("val neg = -42", "string(neg)", "-42") - } - - test("string conversions - with expressions") { - // Test string conversion of arithmetic expressions - assertExecValueString("string(1 + 2)", "3") - assertExecValueString("string(10 - 5)", "5") - assertExecValueString("string(3 * 4)", "12") - assertExecValueString("string(15 / 3)", "5") - assertExecValueString("string(17 % 5)", "2") - - // Test string conversion of boolean expressions - assertExecValueString("string(true && false)", "false") - assertExecValueString("string(true || false)", "true") - assertExecValueString("string(!true)", "false") - assertExecValueString("string(!false)", "true") - assertExecValueString("string(5 > 3)", "true") - assertExecValueString("string(5 == 5)", "true") - assertExecValueString("string(5 != 3)", "true") - assertExecValueString("string(5 < 3)", "false") - - // Test string conversion of unary expressions - assertExecValueString("string(-42)", "-42") - assertExecValueString("string(+42)", "42") - } - - test("string conversions - complex expressions") { - val setup = "val x = 42\nval y = true" - - // String conversions in comparisons - assertExecValueBoolWithSetup(setup, "string(x) == \"42\"", true) - assertExecValueBoolWithSetup(setup, "string(y) == \"true\"", true) - assertExecValueBoolWithSetup(setup, "string(x) != \"0\"", true) - assertExecValueBoolWithSetup(setup, "string(y) != \"false\"", true) - - // String conversions of computed values - assertExecValueStringWithSetup(setup, "string(x + 10)", "52") - assertExecValueStringWithSetup(setup, "string(x * 2)", "84") - assertExecValueStringWithSetup(setup, "string(x - 10)", "32") - } - - test("string conversions - with control flow") { - // String conversions with if expressions - assertExecValueString("string(if (true) 1 else 2)", "1") - assertExecValueString("string(if (false) 1 else 2)", "2") - assertExecValueString("string(if (5 > 3) 100 else 200)", "100") - assertExecValueString("string(if (3 > 5) true else false)", "false") - - // String conversions with block expressions - assertExecValueString("string({ 42 })", "42") - assertExecValueString("string({ true })", "true") - assertExecValueString( - "string({ val temp = 10\n temp * 2 })", - "20" - ) - } - - test("string conversions - nested and chained") { - val setup = "val x = 42" - - // Nested string conversions - assertExecValueStringWithSetup(setup, "string(string(x))", "42") - assertExecValueString("string(string(\"hello\"))", "hello") - - // String conversions in nested expressions - assertExecValueStringWithSetup( - setup, - "string(string(x) == \"42\")", - "true" - ) - assertExecValueStringWithSetup( - setup, - "string(string(x + 8) == \"50\")", - "true" - ) - } - - test("string conversions - edge cases") { - // Large numbers - assertExecValueString("string(999999)", "999999") - assertExecValueString("string(-999999)", "-999999") - - // Complex arithmetic - assertExecValueString("string((10 + 5) * (20 / 4))", "75") - assertExecValueString("string(100 - 50 + 25)", "75") - - // Complex boolean logic - assertExecValueString("string((5 > 3) && (10 < 20))", "true") - assertExecValueString("string((5 < 3) || (10 > 20))", "false") - } - - test("int conversions - basic functionality") { - // Test int conversion of basic literals - assertExecValueInt("int(42)", 42) - assertExecValueInt("int(0)", 0) - assertExecValueInt("int(-123)", -123) - assertExecValueInt("int(true)", 1) - assertExecValueInt("int(false)", 0) - - // Int conversion with variables - assertExecValueIntWithSetup("val flag = true", "int(flag)", 1) - assertExecValueIntWithSetup("val flag2 = false", "int(flag2)", 0) - assertExecValueIntWithSetup("val num = 123", "int(num)", 123) - } - - test("int conversions - string cases") { - assertExecValueInt("int(\"42\")", 42) - assertExecValueInt("int(\"0\")", 0) - assertExecValueInt("int(\"-123\")", -123) - assertExecValueIntWithSetup("val str = \"56\"", "int(str)", 56) - assertExecValueIntWithSetup("val str = \"-78\"", "int(str)", -78) - // Edge cases - assertExecValueInt("int(\"00123\")", 123) - assertExecValueInt("int(\"-00123\")", -123) - assertExecValueInt("int(\"2147483647\")", 2147483647) // Max int - assertExecValueInt("int(\"-2147483648\")", -2147483648) // Min int - } - - test("bool conversions - basic functionality") { - // Test bool conversion of basic literals - assertExecValueBool("bool(true)", true) - assertExecValueBool("bool(false)", false) - assertExecValueBool("bool(42)", true) - assertExecValueBool("bool(0)", false) - assertExecValueBool("bool(-5)", true) - - // Bool conversion with variables - assertExecValueBoolWithSetup("val num = 123", "bool(num)", true) - assertExecValueBoolWithSetup("val zero = 0", "bool(zero)", false) - assertExecValueBoolWithSetup("val flag = true", "bool(flag)", true) - } - - test("conversion edge cases") { - // Test conversion of expressions - assertExecValueString("string(1 + 2 * 3)", "7") - assertExecValueInt("int(5 > 3)", 1) - assertExecValueInt("int(3 > 5)", 0) - assertExecValueBool("bool(10 - 10)", false) - assertExecValueBool("bool(10 - 9)", true) - - // Test nested conversions - assertExecValueString("string(int(true))", "1") - assertExecValueString("string(int(false))", "0") - assertExecValueInt("int(bool(42))", 1) - assertExecValueInt("int(bool(0))", 0) - } - - test("conversions with complex expressions") { - val setup = "val x = 42\nval y = true" - - // Complex conversions - assertExecValueStringWithSetup(setup, "string(int(y) + x)", "43") - assertExecValueIntWithSetup(setup, "int(string(x) == \"42\")", 1) - assertExecValueBoolWithSetup(setup, "bool(int(y) * x)", true) - - // Conversions in control flow - assertExecValueStringWithSetup( - setup, - "string(if (bool(x)) int(y) else 0)", - "1" - ) - } + it should "execute cast expressions - cast with operators" in { + // Test that cast has correct precedence with other operators + assertExecValueBool("42 as int == 42", true) + assertExecValueBool("42 as string == \"42\"", true) + assertExecValueBool("true as bool && false", false) + assertExecValueBool("false as bool || true", true) + + // Test cast in arithmetic expressions + assertExecValueInt("(40 + 2) as int", 42) + assertExecValueInt("40 + 2 as int", 42) // Should be: 40 + (2 as int) + } + + it should "execute cast expressions - cast in control flow" in { + // Test cast expressions in if conditions + assertExecValueInt("if (42 as int == 42) 1 else 0", 1) + assertExecValueInt("if (true as bool) 100 else 200", 100) + assertExecValueInt("if (false as bool) 100 else 200", 200) + + // Test cast in if branches + assertExecValueInt("if (true) 42 as int else 0", 42) + assertExecValueString("if (false) \"no\" else \"yes\" as string", "yes") + + // Test with variables + assertExecValueIntWithSetup( + "val x = 50", + "if (x as int > 40) 1 else 0", + 1 + ) + } + + it should "execute array indexing - basic access" in { + // Test basic array indexing with literal indices + val setup = "val array = new Array[int](3)" + // Note: These tests assume array elements are initialized to 0 + // If that's not the case, we may need to modify the setup + assertExecValueIntWithSetup(setup, "array(0)", 0) + assertExecValueIntWithSetup(setup, "array(1)", 0) + assertExecValueIntWithSetup(setup, "array(2)", 0) + } + + it should "execute array indexing - with assignment" in { + // Test the specific "NewExpression in bindLHS" fix + val setup = "var array = new Array[int](5)" + // First test assignment returns unit + assertExecValueIntWithSetup( + setup + "\narray(0) = 42", + "0", + 0 + ) // dummy assertion to ensure compilation + + // Test assignment followed by access + val assignAndRead = setup + "\narray(0) = 42\narray(1) = 13" + assertExecValueIntWithSetup(assignAndRead, "array(0)", 42) + assertExecValueIntWithSetup(assignAndRead, "array(1)", 13) + } + + it should "execute array indexing - computed indices" in { + val setup = "var array = new Array[int](10)\nval i = 5\narray(i) = 99" + assertExecValueIntWithSetup(setup, "array(i)", 99) + assertExecValueIntWithSetup(setup, "array(5)", 99) + + // Test with arithmetic expressions as indices + val complexSetup = setup + "\narray(2 * 3) = 77\narray(1 + 4) = 55" + assertExecValueIntWithSetup(complexSetup, "array(6)", 77) + assertExecValueIntWithSetup( + complexSetup, + "array(5)", + 55 + ) // This overwrites the previous value at index 5 + } + + it should "execute array indexing - in expressions" in { + val setup = + "var array = new Array[int](5)\narray(0) = 10\narray(1) = 20\narray(2) = 30" + + // Test array indexing in arithmetic expressions + assertExecValueIntWithSetup(setup, "array(0) + array(1)", 30) + assertExecValueIntWithSetup(setup, "array(2) - array(0)", 20) + assertExecValueIntWithSetup(setup, "array(1) * 2", 40) + + // Test array indexing in boolean expressions + assertExecValueBoolWithSetup(setup, "array(0) == 10", true) + assertExecValueBoolWithSetup(setup, "array(1) > array(0)", true) + assertExecValueBoolWithSetup(setup, "array(2) < array(1)", false) + } + + it should "execute array indexing - different types" in { + // Test array indexing with different element types + val boolSetup = + "var boolArray = new Array[bool](3)\nboolArray(0) = true\nboolArray(1) = false" + assertExecValueBoolWithSetup(boolSetup, "boolArray(0)", true) + assertExecValueBoolWithSetup(boolSetup, "boolArray(1)", false) + + val stringSetup = + "var stringArray = new Array[string](2)\nstringArray(0) = \"hello\"\nstringArray(1) = \"world\"" + assertExecValueStringWithSetup(stringSetup, "stringArray(0)", "hello") + assertExecValueStringWithSetup(stringSetup, "stringArray(1)", "world") + } + + it should "execute string conversions - basic literals" in { + // Test string conversion of basic literals + assertExecValueString("string(42)", "42") + assertExecValueString("string(0)", "0") + assertExecValueString("string(-123)", "-123") + assertExecValueString("string(true)", "true") + assertExecValueString("string(false)", "false") + + // String conversion of string (identity) + assertExecValueString("string(\"hello\")", "hello") + assertExecValueString("string(\"\")", "") + } + + it should "execute string conversions - with variables" in { + // Test string conversion of variables + assertExecValueStringWithSetup("val num = 123", "string(num)", "123") + assertExecValueStringWithSetup("val flag = true", "string(flag)", "true") + assertExecValueStringWithSetup( + "val flag2 = false", + "string(flag2)", + "false" + ) + assertExecValueStringWithSetup( + "val text = \"world\"", + "string(text)", + "world" + ) + + // Negative numbers + assertExecValueStringWithSetup("val neg = -42", "string(neg)", "-42") + } + + it should "execute string conversions - with expressions" in { + // Test string conversion of arithmetic expressions + assertExecValueString("string(1 + 2)", "3") + assertExecValueString("string(10 - 5)", "5") + assertExecValueString("string(3 * 4)", "12") + assertExecValueString("string(15 / 3)", "5") + assertExecValueString("string(17 % 5)", "2") + + // Test string conversion of boolean expressions + assertExecValueString("string(true && false)", "false") + assertExecValueString("string(true || false)", "true") + assertExecValueString("string(!true)", "false") + assertExecValueString("string(!false)", "true") + assertExecValueString("string(5 > 3)", "true") + assertExecValueString("string(5 == 5)", "true") + assertExecValueString("string(5 != 3)", "true") + assertExecValueString("string(5 < 3)", "false") + + // Test string conversion of unary expressions + assertExecValueString("string(-42)", "-42") + assertExecValueString("string(+42)", "42") + } + + it should "execute string conversions - complex expressions" in { + val setup = "val x = 42\nval y = true" + + // String conversions in comparisons + assertExecValueBoolWithSetup(setup, "string(x) == \"42\"", true) + assertExecValueBoolWithSetup(setup, "string(y) == \"true\"", true) + assertExecValueBoolWithSetup(setup, "string(x) != \"0\"", true) + assertExecValueBoolWithSetup(setup, "string(y) != \"false\"", true) + + // String conversions of computed values + assertExecValueStringWithSetup(setup, "string(x + 10)", "52") + assertExecValueStringWithSetup(setup, "string(x * 2)", "84") + assertExecValueStringWithSetup(setup, "string(x - 10)", "32") + } + + it should "execute string conversions - with control flow" in { + // String conversions with if expressions + assertExecValueString("string(if (true) 1 else 2)", "1") + assertExecValueString("string(if (false) 1 else 2)", "2") + assertExecValueString("string(if (5 > 3) 100 else 200)", "100") + assertExecValueString("string(if (3 > 5) true else false)", "false") + + // String conversions with block expressions + assertExecValueString("string({ 42 })", "42") + assertExecValueString("string({ true })", "true") + assertExecValueString( + "string({ val temp = 10\n temp * 2 })", + "20" + ) + } + + it should "execute string conversions - nested and chained" in { + val setup = "val x = 42" + + // Nested string conversions + assertExecValueStringWithSetup(setup, "string(string(x))", "42") + assertExecValueString("string(string(\"hello\"))", "hello") + + // String conversions in nested expressions + assertExecValueStringWithSetup( + setup, + "string(string(x) == \"42\")", + "true" + ) + assertExecValueStringWithSetup( + setup, + "string(string(x + 8) == \"50\")", + "true" + ) + } + + it should "execute string conversions - edge cases" in { + // Large numbers + assertExecValueString("string(999999)", "999999") + assertExecValueString("string(-999999)", "-999999") + + // Complex arithmetic + assertExecValueString("string((10 + 5) * (20 / 4))", "75") + assertExecValueString("string(100 - 50 + 25)", "75") + + // Complex boolean logic + assertExecValueString("string((5 > 3) && (10 < 20))", "true") + assertExecValueString("string((5 < 3) || (10 > 20))", "false") + } + + it should "execute int conversions - basic functionality" in { + // Test int conversion of basic literals + assertExecValueInt("int(42)", 42) + assertExecValueInt("int(0)", 0) + assertExecValueInt("int(-123)", -123) + assertExecValueInt("int(true)", 1) + assertExecValueInt("int(false)", 0) + + // Int conversion with variables + assertExecValueIntWithSetup("val flag = true", "int(flag)", 1) + assertExecValueIntWithSetup("val flag2 = false", "int(flag2)", 0) + assertExecValueIntWithSetup("val num = 123", "int(num)", 123) + } + + it should "execute int conversions - string cases" in { + assertExecValueInt("int(\"42\")", 42) + assertExecValueInt("int(\"0\")", 0) + assertExecValueInt("int(\"-123\")", -123) + assertExecValueIntWithSetup("val str = \"56\"", "int(str)", 56) + assertExecValueIntWithSetup("val str = \"-78\"", "int(str)", -78) + // Edge cases + assertExecValueInt("int(\"00123\")", 123) + assertExecValueInt("int(\"-00123\")", -123) + assertExecValueInt("int(\"2147483647\")", 2147483647) // Max int + assertExecValueInt("int(\"-2147483648\")", -2147483648) // Min int + } + + it should "execute bool conversions - basic functionality" in { + // Test bool conversion of basic literals + assertExecValueBool("bool(true)", true) + assertExecValueBool("bool(false)", false) + assertExecValueBool("bool(42)", true) + assertExecValueBool("bool(0)", false) + assertExecValueBool("bool(-5)", true) + + // Bool conversion with variables + assertExecValueBoolWithSetup("val num = 123", "bool(num)", true) + assertExecValueBoolWithSetup("val zero = 0", "bool(zero)", false) + assertExecValueBoolWithSetup("val flag = true", "bool(flag)", true) + } + + it should "execute conversion edge cases" in { + // Test conversion of expressions + assertExecValueString("string(1 + 2 * 3)", "7") + assertExecValueInt("int(5 > 3)", 1) + assertExecValueInt("int(3 > 5)", 0) + assertExecValueBool("bool(10 - 10)", false) + assertExecValueBool("bool(10 - 9)", true) + + // Test nested conversions + assertExecValueString("string(int(true))", "1") + assertExecValueString("string(int(false))", "0") + assertExecValueInt("int(bool(42))", 1) + assertExecValueInt("int(bool(0))", 0) + } + + it should "execute conversions with complex expressions" in { + val setup = "val x = 42\nval y = true" + + // Complex conversions + assertExecValueStringWithSetup(setup, "string(int(y) + x)", "43") + assertExecValueIntWithSetup(setup, "int(string(x) == \"42\")", 1) + assertExecValueBoolWithSetup(setup, "bool(int(y) * x)", true) + + // Conversions in control flow + assertExecValueStringWithSetup( + setup, + "string(if (bool(x)) int(y) else 0)", + "1" + ) } }