/* * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * Mini binding tests for highlighting/editor features */ package net.sergeych.lyng import kotlinx.coroutines.test.runTest import net.sergeych.lyng.binding.Binder import net.sergeych.lyng.miniast.MiniAstBuilder import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue class BindingTest { private suspend fun bind(code: String): net.sergeych.lyng.binding.BindingSnapshot { val src = code.trimIndent() val sink = MiniAstBuilder() Compiler.compileWithMini(src, sink) val mini = sink.build() assertNotNull(mini, "MiniScript should be built") return Binder.bind(src, mini) } @Test fun binds_params_and_locals() = runTest { val snap = bind( """ fun f(a:Int){ val x = 1 a + x } """ ) // Expect at least one Parameter symbol "a" and one Value symbol "x" val aIds = snap.symbols.filter { it.name == "a" }.map { it.id } val xIds = snap.symbols.filter { it.name == "x" }.map { it.id } assertTrue(aIds.isNotEmpty()) assertTrue(xIds.isNotEmpty()) // Both should have at least one reference across any symbol with that name val aRefs = snap.references.count { it.symbolId in aIds } val xRefs = snap.references.count { it.symbolId in xIds } assertEquals(1, aRefs) assertEquals(1, xRefs) } @Test fun binds_top_level_val_usage() = runTest { val snap = bind( """ val x = 1 x + 1 """ ) val xSym = snap.symbols.firstOrNull { it.name == "x" } assertNotNull(xSym) // One reference usage to top-level x val refs = snap.references.filter { it.symbolId == xSym.id } assertEquals(1, refs.size) } @Test fun shadowing_scopes() = runTest { val snap = bind( """ val x = 1 fun f(){ val x = 2 x } """ ) val allX = snap.symbols.filter { it.name == "x" } // Expect at least two x symbols (top-level and local) assertEquals(true, allX.size >= 2) // The single reference inside f body should bind to the inner x (containerId != null) val localXs = allX.filter { it.containerId != null } assertEquals(true, localXs.isNotEmpty()) val localX = localXs.maxBy { it.declStart } val refsToLocal = snap.references.count { it.symbolId == localX.id } assertEquals(1, refsToLocal) } @Test fun class_fields_basic() = runTest { val snap = bind( """ class C { val foo = 1 fun bar(){ foo } } """ ) val fooField = snap.symbols.firstOrNull { it.name == "foo" } assertNotNull(fooField) // Should have at least one reference (usage in bar) val refs = snap.references.count { it.symbolId == fooField.id } assertEquals(1, refs) } @Test fun ctor_params_as_fields() = runTest { val snap = bind( """ class C(val x:Int){ fun f(){ x } } """ ) val xField = snap.symbols.firstOrNull { it.name == "x" } assertNotNull(xField) val refs = snap.references.count { it.symbolId == xField.id } assertEquals(1, refs) } class ObjA: Obj() { override val objClass = type companion object { val type = ObjClass("ObjA").apply { addFn("get1") { ObjString("get1") } } } } @Test fun testShortFormMethod() = runTest { eval(""" class A { fun get1() = "1" fun get2() = get1() + "-2" fun get3(): String = get2() + "-3" override fun toString() = "!"+get3()+"!" } assert(A().get3() == "1-2-3") assert(A().toString() == "!1-2-3!") """.trimIndent()) } @Test fun testLateGlobalBinding() = runTest { val ms = Script.newScope() ms.eval(""" extern class A { extern fun get1(): String } extern fun getA(): A fun getB(a: A) = a.get1() + "-2" """.trimIndent()) ms.addFn("getA") { ObjA() } ms.addConst("A", ObjA.type) ms.eval(""" assert(A() is A) assert(getA() is A) assertEquals(getB(getA()), "get1-2") """.trimIndent()) } }