# What's New in Lyng

This document highlights the latest additions and improvements to the Lyng language and its ecosystem.
For a programmer-focused migration summary, see `docs/whats_new_1_3.md`.

## Language Features

### Class Properties with Accessors
Classes now support properties with custom `get()` and `set()` accessors. Properties in Lyng do **not** have automatic backing fields; they are pure accessors.

```lyng
class Person(private var _age: Int) {
    // Read-only property
    val ageCategory get() = if (_age < 18) "Minor" else "Adult"

    // Read-write property
    var age: Int
        get() = _age
        set(v) {
            if (v >= 0) _age = v
        }
}
```

### Private and Protected Setters
You can now restrict the visibility of a property's or field's setter using `private set` or `protected set`. This allows members to be publicly readable but only writable from within the declaring class or its subclasses.

### Refined Protected Visibility
Ancestor classes can now access `protected` members of their descendants if it is an override of a member known to the ancestor. This enables base classes to call protected methods that are implemented or overridden in subclasses.

```lyng
class Counter {
    var count = 0
        private set  // Field with private setter
        
    fun increment() { count++ }
}

class AdvancedCounter : Counter {
    var totalOperations = 0
        protected set // Settable here and in further subclasses
}

let c = Counter()
c.increment() // OK
// c.count = 10 // Error: setter is private
```

### Late-initialized `val` Fields
`val` fields in classes can be declared without an immediate initializer, provided they are assigned exactly once. If accessed before initialization, they hold the special `Unset` singleton.

```lyng
class Service {
    val logger
    
    fun check() {
        if (logger == Unset) println("Not initialized yet")
    }

    init {
        logger = Logger("Service")
    }
}
```

### Named Arguments and Named Splats
Function calls now support named arguments using the `name: value` syntax. If the variable name matches the parameter name, you can use the `name:` shorthand.

```lyng
fun greet(name, greeting = "Hello") {
    println("$greeting, $name!")
}

val name = "Alice"
greet(name:)                  // Shorthand for greet(name: name)
greet(greeting: "Hi", name: "Bob")

let params = { name: "Charlie", greeting: "Hey")
greet(...params)              // Named splat expansion
```

### Multiple Inheritance (MI)
Lyng now supports multiple inheritance using the C3 Method Resolution Order (MRO). Use `this@Type` or casts for disambiguation.

```lyng
class A { fun foo() = "A" }
class B { fun foo() = "B" }

class Derived : A, B {
    fun test() {
        println(foo())              // Resolves to A.foo (leftmost)
        println(this@B.foo())       // Qualified dispatch to B.foo
    }
}

let d = Derived()
println((d as B).foo())             // Disambiguation via cast
```

### Singleton Objects
Singleton objects are declared using the `object` keyword. They provide a convenient way to define a class and its single instance in one go.

```lyng
object Config {
    val version = "1.2.3"
    fun show() = println("Config version: " + version)
}

Config.show()
```

### Nested Declarations and Lifted Enums
You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier.

```lyng
class A {
    class B(x?)
    object Inner { val foo = "bar" }
    enum E* { One, Two }
}

val ab = A.B()
assertEquals(ab.x, null)
assertEquals(A.Inner.foo, "bar")
assertEquals(A.One, A.E.One)
```

The `*` on `enum E*` lifts entries into the enclosing class namespace (compile-time error on ambiguity).

### Object Expressions
You can now create anonymous objects that inherit from classes or interfaces using the `object : Base { ... }` syntax. These expressions capture their lexical scope and support multiple inheritance.

```lyng
val worker = object : Runnable {
    override fun run() = println("Working...")
}

val x = object : Base(arg1), Interface1 {
    val property = 42
    override fun method() = this@object.property * 2
}
```

Use `this@object` to refer to the innermost anonymous object instance when `this` is rebound.

### Unified Delegation Model
A powerful new delegation system allows `val`, `var`, and `fun` members to delegate their logic to other objects using the `by` keyword.

```lyng
// Property delegation
val lazyValue by lazy { "expensive" }

// Function delegation
fun remoteAction by myProxy

// Observable properties
var name by Observable("initial") { n, old, new -> 
    println("Changed!") 
}
```

The system features a unified interface (`getValue`, `setValue`, `invoke`) and a `bind` hook for initialization-time validation and configuration. See the [Delegation Guide](delegation.md) for more.

### User-Defined Exception Classes
You can now create custom exception types by inheriting from the built-in `Exception` class. Custom exceptions are real classes that can have their own fields and methods, and they work seamlessly with `throw` and `try-catch` blocks.

```lyng
class ValidationException(val field, m) : Exception(m)

try {
    throw ValidationException("email", "Invalid format")
}
catch(e: ValidationException) {
    println("Error in " + e.field + ": " + e.message)
}
```

### Assign-if-null Operator (`?=`)
The new `?=` operator provides a concise way to assign a value only if the target is `null`. It is especially useful for setting default values or lazy initialization.

```lyng
var x = null
x ?= 42          // x is now 42
x ?= 100         // x remains 42 (not null)

// Works with properties and index access
config.port ?= 8080
settings["theme"] ?= "dark"
```

The operator returns the final value of the receiver (the original value if it was not `null`, or the new value if the assignment occurred).

### Transient Attribute (`@Transient`)
The `@Transient` attribute can now be applied to class fields, constructor parameters, and static fields to exclude them from serialization.

```lyng
class MyData(@Transient val tempSecret, val publicData) {
    @Transient var cachedValue = 0
    var persistentValue = 42
}
```

Key features:
- **Serialization**: Transient members are omitted from both Lynon binary streams and JSON output.
- **Structural Equality**: Transient fields are automatically ignored during `==` equality checks.
- **Deserialization**: Transient constructor parameters with default values are correctly restored to those defaults upon restoration.

### Value Clamping (`clamp`)
A new `clamp()` function has been added to the standard library to limit a value within a specified range. It is available as both a global function and an extension method on all objects.

```lyng
// Global function
clamp(15, 0..10)    // returns 10
clamp(-5, 0..10)   // returns 0

// Extension method
val x = 15
x.clamp(0..10)      // returns 10

// Exclusive and open-ended ranges
15.clamp(0..<10)    // returns 9
15.clamp(..10)      // returns 10
-5.clamp(0..)       // returns 0
```

`clamp()` correctly handles inclusive (`..`) and exclusive (`..<`) ranges. For discrete types like `Int` and `Char`, clamping to an exclusive upper bound returns the previous value.

## Tooling and Infrastructure

### CLI: Formatting Command
A new `fmt` subcommand has been added to the Lyng CLI.

```bash
lyng fmt MyFile.lyng             # Print formatted code to stdout
lyng fmt --in-place MyFile.lyng  # Format file in-place
lyng fmt --check MyFile.lyng     # Check if file needs formatting
```

### IDEA Plugin: Autocompletion
Experimental lightweight autocompletion is now available in the IntelliJ plugin. It features type-aware member suggestions and inheritance-aware completion.

You can enable it in **Settings | Lyng Formatter | Enable Lyng autocompletion**.

### Kotlin API: Exception Handling
The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been added to simplify retrieving detailed error information from Lyng exception objects in Kotlin. Additionally, `getLyngExceptionMessage()` and `raiseAsExecutionError()` now accept an optional `Scope`, making it easier to use them when a scope is not immediately available.
