From b6851b70987b36f3b216288bf78e4de7dc1a8e6d Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 17 Feb 2026 08:06:06 +0300 Subject: [PATCH] Improve variable resolution and expand test coverage; improved wahtsnew --- docs/whats_new_1_5.md | 63 ++++++++++++++++--- .../lyng/bytecode/BytecodeCompiler.kt | 27 +++++++- .../lyng/miniast/StdlibDocsBootstrap.kt | 30 +++++++++ lynglib/src/commonTest/kotlin/StdlibTest.kt | 36 +++++++---- 4 files changed, 134 insertions(+), 22 deletions(-) diff --git a/docs/whats_new_1_5.md b/docs/whats_new_1_5.md index ae9431a..4edfdf7 100644 --- a/docs/whats_new_1_5.md +++ b/docs/whats_new_1_5.md @@ -1,21 +1,48 @@ # What's New in Lyng 1.5 (vs 1.3.* / master) -This document summarizes the significant changes and new features introduced in the 1.5.0-SNAPSHOT development cycle. +This document summarizes the significant changes and new features introduced in the 1.5 development cycle. -## Highlights +## Principal changes + +### JIT compiler and compile-time types and symbols. + +This major improvement gives the following big advantages: + +- **Blazing Fast execution**: several times faster! (three to six times speedup in different scenarios). +- **Better IDE support**: autocompletion, early error detection, types check. +- **Error safety**: all symbols and types are checked at bound at compile-time. Many errors are detected earlier. Also, no risk that external or caller code would shadow some internally used symbols (especially in closures and inheritance). + +In particular, it means no slow and flaky runtime lookups. Once compiled, code guarantees that it will always call the symbol known at compile-time; runtime name lookup though does not guarantee it and can be source of hard to trace bugs. + +### New stable API to create Kotlin extensions + +The API is fixed and will be kept with further Lyng core changes. It is now the recommended way to write Lyng extensions in Kotlin. It is much simpler and more elegant than the internal one. See [Kotlin Bridge Binding](../notes/kotlin_bridge_binding.md). + +### Smart types system + +- **Deep inference**: The compiler analyzes types of symbols along the execution path and in many cases eliminates unnecessary casts or type specifications. +- **Union and intersection types**: `A & B`, `A | B`. +- **Generics**: Generic types are first-class citizens with support for [bounds and variance](generics.md). No type erasure: in a generic function you can, for example, check `A in T`, where T is the generic type. +- **Inner classes and enums**: Full support for nested declarations, including [Enums with lifting](OOP.md#lifted-enum-entries). + +## Other highlights - **The `return` Statement**: Added support for local and non-local returns using labels. - **Abstract Classes and Interfaces**: Full support for `abstract` members and the `interface` keyword. +- **Singleton Objects**: Define singletons using the `object` keyword or use anonymous object expressions. +- **Multiple Inheritance**: Enhanced multi-inheritance with predictable [C3 MRO resolution](OOP.md#multiple-inheritance-and-mro). +- **Unified Delegation**: Powerful delegation model for `val`, `var`, and `fun` members. See [Delegation](delegation.md). - **Class Properties with Accessors**: Define `val` and `var` properties with custom `get()` and `set()`. - **Restricted Setter Visibility**: Use `private set` and `protected set` on fields and properties. - **Late-initialized `val`**: Support for `val` fields that are initialized in `init` blocks or class bodies. +- **Transient Members**: Use `@Transient` to exclude members from serialization and equality checks. - **Named Arguments and Splats**: Improved call-site readability with `name: value` and map-based splats. - **Refined Visibility**: Improved `protected` access and `closed` modifier for better encapsulation. ## Language Features ### The `return` Statement -You can now exit from the innermost enclosing callable (function or lambda) using `return`. Lyng also supports non-local returns to outer scopes using labels. +You can now exit from the innermost enclosing callable (function or lambda) using `return`. Lyng also supports non-local returns to outer scopes using labels. See [Return Statement](return_statement.md). ```lyng fun findFirst(list: Iterable, predicate: (T)->Bool): T? { @@ -56,6 +83,19 @@ class Rectangle(var width: Real, var height: Real) { } ``` +### Singleton Objects +Declare singletons or anonymous objects easily. + +```lyng +object Database { + val connection = "connected" +} + +val runner = object : Runnable { + override fun run() = println("Running!") +} +``` + ### Named Arguments and Named Splats Improve call-site clarity by specifying argument names. You can also expand a Map into named arguments using the splat operator. @@ -63,7 +103,7 @@ Improve call-site clarity by specifying argument names. You can also expand a Ma fun configure(timeout: Int, retry: Int = 3) { ... } configure(timeout: 5000, retry: 5) -val options = { timeout: 1000, retry: 1 } +val options = Map("timeout": 1000, "retry": 1) configure(...options) ``` @@ -72,7 +112,7 @@ The `?=` operator allows for concise "assign if null" logic. ```lyng var cache: Map? = null -cache ?= { "status": "ok" } // Only assigns if cache is null +cache ?= Map("status": "ok") // Only assigns if cache is null ``` ## Tooling and IDE @@ -81,13 +121,20 @@ cache ?= { "status": "ok" } // Only assigns if cache is null - **CLI**: The `lyng fmt` command is now a first-class tool for formatting code with various options like `--check` and `--in-place`. - **Performance**: Ongoing optimizations in the bytecode VM and compiler for faster execution and smaller footprint. +## Standard Library +- **`with(self, block)`**: Scoped execution with a dedicated `self`. +- **`clamp(value, min, max)`**: Easily restrict values to a range. + ## Migration Guide (from 1.3.*) 1. **Check Visibility**: Refined `protected` and `private` rules may catch previously undetected invalid accesses. 2. **Override Keyword**: Ensure all members that override ancestor declarations are marked with the `override` keyword (now mandatory). 3. **Return in Shorthand**: Remember that `return` is forbidden in `=` shorthand functions; use block syntax if you need early exit. +4. **Empty Map Literals**: Use `Map()` or `{:}` for empty maps, as `{}` is now strictly a block/lambda. ## References -- `docs/OOP.md` -- `docs/return_statement.md` -- `docs/tutorial.md` +- [Object Oriented Programming](OOP.md) +- [Generics](generics.md) +- [Return Statement](return_statement.md) +- [Delegation](delegation.md) +- [Tutorial](tutorial.md) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 29d618a..a145b92 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -7113,9 +7113,30 @@ class BytecodeCompiler( private fun resolveReceiverClassForScopeCollection(ref: ObjRef): ObjClass? { return when (ref) { - is LocalSlotRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name) - is LocalVarRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name) - is FastLocalVarRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name) + is LocalSlotRef -> { + val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId + val ownerSlot = ref.captureOwnerSlot ?: ref.slot + slotTypeByScopeId[ownerScopeId]?.get(ownerSlot) + ?: slotInitClassByKey[ScopeSlotKey(ownerScopeId, ownerSlot)] + ?: nameObjClass[ref.name] + ?: resolveTypeNameClass(ref.name) + } + is LocalVarRef -> { + val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key + key?.let { + slotTypeByScopeId[it.scopeId]?.get(it.slot) + ?: slotInitClassByKey[it] + } ?: nameObjClass[ref.name] + ?: resolveTypeNameClass(ref.name) + } + is FastLocalVarRef -> { + val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key + key?.let { + slotTypeByScopeId[it.scopeId]?.get(it.slot) + ?: slotInitClassByKey[it] + } ?: nameObjClass[ref.name] + ?: resolveTypeNameClass(ref.name) + } is BoundLocalVarRef -> slotObjClass[ref.slotIndex()] is QualifiedThisRef -> resolveTypeNameClass(ref.typeName) is ListLiteralRef -> ObjList.type diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/StdlibDocsBootstrap.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/StdlibDocsBootstrap.kt index 171ba2a..0c4f0f9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/StdlibDocsBootstrap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/StdlibDocsBootstrap.kt @@ -1,3 +1,20 @@ +/* + * 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. + * + */ + /* * Ensure stdlib Obj*-defined docs (like String methods added via ObjString.addFnDoc) * are initialized before registry lookups for completion/quick docs. @@ -33,6 +50,19 @@ object StdlibDocsBootstrap { val _range = net.sergeych.lyng.obj.ObjRange.type @Suppress("UNUSED_VARIABLE") val _buffer = net.sergeych.lyng.obj.ObjBuffer.type + + // Also touch time module types so their docs (moduleName = "lyng.time") are registered + // This enables completion/quick docs for symbols imported via `import lyng.time` (e.g., Instant, DateTime, Duration) + try { + @Suppress("UNUSED_VARIABLE") + val _instant = net.sergeych.lyng.obj.ObjInstant.type + @Suppress("UNUSED_VARIABLE") + val _datetime = net.sergeych.lyng.obj.ObjDateTime.type + @Suppress("UNUSED_VARIABLE") + val _duration = net.sergeych.lyng.obj.ObjDuration.type + } catch (_: Throwable) { + // Optional; absence should not affect stdlib core + } } catch (_: Throwable) { // Best-effort; absence should not break consumers } finally { diff --git a/lynglib/src/commonTest/kotlin/StdlibTest.kt b/lynglib/src/commonTest/kotlin/StdlibTest.kt index afa0b7d..5c2894d 100644 --- a/lynglib/src/commonTest/kotlin/StdlibTest.kt +++ b/lynglib/src/commonTest/kotlin/StdlibTest.kt @@ -132,15 +132,29 @@ class StdlibTest { """.trimIndent()) } -// @Test -// fun testFunFromSample() = runTest { -// range should be iterable if it is intrange -// eval(""" -// val data = 1..5 // or [1, 2, 3, 4, 5] -// fun test() { -// data.filter { it % 2 == 0 }.map { it * it } -// } -// test() -// """.trimIndent()) -// } + @Test + fun testFilter1() = runTest { + // range should be iterable if it is intrange + eval(""" + val data = 1..5 // or [1, 2, 3, 4, 5] + assert( data is Iterable ) + fun test() { + data.filter { it % 2 == 0 }.map { it * it } + } + test() + """.trimIndent()) + } + + @Test + fun testFilter2() = runTest { + eval(""" + val data = [1, 2, 3, 4, 5] + assert( data is Iterable ) + fun test() { + data.filter { it % 2 == 0 }.map { it * it } + } + test() + """ + ) + } }