From 1fab2702dd963fe0ec16450dd687c8c7781289c4 Mon Sep 17 00:00:00 2001 From: sergeych Date: Wed, 4 Feb 2026 03:40:07 +0300 Subject: [PATCH] Allow Object members on unknown types --- AGENTS.md | 4 +-- LYNG_AI_SPEC.md | 14 +++++++-- .../lyng/bytecode/BytecodeCompiler.kt | 12 +++++--- lynglib/stdlib/lyng/root.lyng | 4 +-- notes/ai_state.md | 30 +++++-------------- notes/bytecode_callsite_fix.md | 3 +- notes/new_lyng_type_system_spec.md | 2 +- 7 files changed, 34 insertions(+), 35 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index eb657db..40c4fb2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,8 +7,8 @@ - If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed. - Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead. -## Type inference notes (notes/type_system_spec.md) +## Type inference notes (notes/new_lyng_type_system_spec.md) - Nullability is Kotlin-style: `T` non-null, `T?` nullable, `!!` asserts non-null. - `void` is a singleton of class `Void` (syntax sugar for return type). -- Object member access requires explicit cast; remove `inspect` from Object and use `toInspectString()` instead. +- Object members are always allowed even on unknown types; non-Object members require explicit casts. Remove `inspect` from Object and use `toInspectString()` instead. - Do not reintroduce bytecode fallback opcodes (e.g., `GET_NAME`, `EVAL_*`, `CALL_FALLBACK`) or runtime name-resolution fallbacks; all symbol resolution must stay compile-time only. diff --git a/LYNG_AI_SPEC.md b/LYNG_AI_SPEC.md index 63a13ca..8137744 100644 --- a/LYNG_AI_SPEC.md +++ b/LYNG_AI_SPEC.md @@ -19,6 +19,7 @@ High-density specification for LLMs. Reference this for all Lyng code generation - **Equality**: `==` (equals), `!=` (not equals), `===` (ref identity), `!==` (ref not identity). - **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1). - **Destructuring**: `val [a, b, rest...] = list`. Supports nested `[a, [b, c]]` and splats. +- **Compile-Time Resolution Only**: All names/members must resolve at compile time. No runtime name lookup or fallback opcodes. ## 2. Object-Oriented Programming (OOP) - **Multiple Inheritance**: Supported with **C3 MRO** (Python-style). Diamond-safe. @@ -37,6 +38,15 @@ High-density specification for LLMs. Reference this for all Lyng code generation - **Disambiguation**: `this@Base.member()` or `(obj as Base).member()`. `as` returns a qualified view. - **Abstract/Interface**: `interface` is a synonym for `abstract class`. Both support state and constructors. - **Extensions**: `fun Class.ext()` or `val Class.ext get = ...`. Scope-isolated. +- **Member Access**: Object members (`toString`, `toInspectString`, `let`, `also`, `apply`, `run`) are allowed on unknown types; all other members require a statically known receiver type or explicit cast. + +## 2.1 Type System (2026) +- **Root Type**: Everything is an `Object` (root of the hierarchy). +- **Nullability**: Non-null by default (`T`), nullable with `T?`, `!!` asserts non-null. +- **Untyped params**: `fun foo(x)` -> `x: Object`, `fun foo(x?)` -> `x: Object?`. +- **Untyped vars**: `var x` is `Unset` until first assignment locks the type. +- **Inference**: List/map literals infer union element types; empty list is `List`, empty map is `{:}`. +- **Generics**: Bounds with `T: A & B` or `T: A | B`; variance uses `out`/`in`. ## 3. Delegation (`by`) Unified model for `val`, `var`, and `fun`. @@ -63,11 +73,11 @@ Delegate Methods: - **Collections**: `List` ( `[a, b]` ), `Map` ( `Map(k => v)` ), `Set` ( `Set(a, b)` ). `MapEntry` ( `k => v` ). ## 5. Patterns & Shorthands -- **Map Literals**: `{ key: value, identifier: }` (identifier shorthand `x:` is `x: x`). Empty map is `Map()`. +- **Map Literals**: `{ key: value, identifier: }` (identifier shorthand `x:` is `x: x`). Empty map is `{:}`. - **Named Arguments**: `fun(y: 10, x: 5)`. Shorthand: `Point(x:, y:)`. - **Varargs & Splats**: `fun f(args...)`, `f(...otherList)`. - **Labels**: `loop@ for(x in list) { if(x == 0) break@loop }`. -- **Dynamic**: `val d = dynamic { get { name -> ... } }` allows `d.anyName`. +- **Dynamic**: `val d = dynamic { get { name -> ... } }` allows `d.anyName` via explicit dynamic handler (not implicit fallback). ## 6. Operators & Methods to Overload | Op | Method | Op | Method | 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 d60ee8c..bc3329f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -2027,10 +2027,14 @@ class BytecodeCompiler( private fun compileFieldRef(ref: FieldRef): CompiledValue? { val receiverClass = resolveReceiverClass(ref.target) - ?: throw BytecodeCompileException( - "Member access requires compile-time receiver type: ${ref.name}", - Pos.builtIn - ) + ?: if (isAllowedObjectMember(ref.name)) { + Obj.rootObjectType + } else { + throw BytecodeCompileException( + "Member access requires compile-time receiver type: ${ref.name}", + Pos.builtIn + ) + } val fieldId = receiverClass.instanceFieldIdMap()[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null diff --git a/lynglib/stdlib/lyng/root.lyng b/lynglib/stdlib/lyng/root.lyng index 5f2c2dc..62cf8ae 100644 --- a/lynglib/stdlib/lyng/root.lyng +++ b/lynglib/stdlib/lyng/root.lyng @@ -291,7 +291,7 @@ override fun List.toString() { var result = "[" for (item in this) { if (!first) result += "," - result += (item as Object).toString() + result += item.toString() first = false } result + "]" @@ -311,7 +311,7 @@ fun List.sort(): Void { fun Exception.printStackTrace(): Void { println(this) for( entry in stackTrace ) { - println("\tat "+(entry as Object).toString()) + println("\tat "+entry.toString()) } } diff --git a/notes/ai_state.md b/notes/ai_state.md index 328b698..6955a86 100644 --- a/notes/ai_state.md +++ b/notes/ai_state.md @@ -6,32 +6,16 @@ Module focus: :lynglib Current focus - Enforce compile-time name/member resolution only; no runtime scope lookup or fallback. - Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT). -- Fallbacks are forbidden and throw BytecodeFallbackException. +- Runtime lookup opcodes (CALL_VIRTUAL/GET_FIELD/SET_FIELD) and fallback callsites are removed. Key recent changes -- Added memberId storage and lookup on ObjClass/ObjInstance; compiler emits memberId ops. -- Removed GET_THIS_MEMBER/SET_THIS_MEMBER usage from bytecode. -- Added ObjIterable.iterator() abstract method to ensure methodId exists. -- Bytecode compiler now throws if receiver type is unknown for member calls. -- Added static receiver inference attempts (name/slot maps), but still failing for stdlib list usage. -- Compiler now wraps function bodies into BytecodeStatement when possible. -- VarDeclStatement now includes initializerObjClass (compile-time type hint). +- Removed method callsite PICs and fallback opcodes; bytecode now relies on compile-time member ids only. +- Operator dispatch emits memberId calls when known; falls back to Obj opcodes for allowed built-ins without name lookup. +- Object members are allowed on unknown types; other members still require a statically known receiver type. +- Renamed BytecodeFallbackException to BytecodeCompileException. -Known failing test -- ScriptTest.testForInIterableUnknownTypeDisasm (jvmTest) - Failure: BytecodeFallbackException "Member call requires compile-time receiver type: add" - Location: stdlib root.lyng filter() -> "val result = []" then "result.add(item)" - Current debug shows LocalSlotRef(result) slotClass/nameClass/initClass all null. - -Likely cause -- Initializer type inference for "val result = []" in stdlib is not reaching BytecodeCompiler: - - stdlib is generated at build time; initializer is wrapped in BytecodeStatement and losing ListLiteralRef info. - - Need a stable compile-time type hint from the compiler or a way to preserve list literal info. - -Potential fixes to pursue -1) Preserve ObjRef for var initializers (e.g., store initializer ref/type in VarDeclStatement). -2) When initializer is BytecodeStatement, use its CmdFunction to detect list/range literal usage. -3) Ensure stdlib compilation sets initializerObjClass for list literals. +Known failing tests +- None (jvmTest passing). Files touched recently - notes/type_system_spec.md (spec updated) diff --git a/notes/bytecode_callsite_fix.md b/notes/bytecode_callsite_fix.md index 149f6a7..7a48f7b 100644 --- a/notes/bytecode_callsite_fix.md +++ b/notes/bytecode_callsite_fix.md @@ -1,10 +1,11 @@ -# Bytecode call-site PIC + fallback gating +# Bytecode call-site PIC + fallback gating (obsolete) Changes - Added method call PIC path in bytecode VM with new CALL_SLOT/CALL_VIRTUAL opcodes. - Fixed FieldRef property/delegate resolution to avoid bypassing ObjRecord delegation. - Prevent delegated ObjRecord mutation by returning a resolved copy. - Restricted bytecode call compilation to args that are ExpressionStatement (no splat/named/tail-block), fallback otherwise. +- NOTE: PICs and fallback callsites have been removed; keep this for historical context only. Rationale - Fixes JVM test regressions and avoids premature evaluation of Statement args. diff --git a/notes/new_lyng_type_system_spec.md b/notes/new_lyng_type_system_spec.md index 8206253..a50e6ea 100644 --- a/notes/new_lyng_type_system_spec.md +++ b/notes/new_lyng_type_system_spec.md @@ -212,7 +212,7 @@ Type params are erased by default. Hidden `Class` args are only injected when a - Member access rules: If a variable is Object (dynamic), is member access a compile-time error, or allowed with fallback (which we are trying to remove)? If error, do we require explicit cast first? -Compile time error unless it is an Object own method. Let's force rewriting existing code in favor of explicit casts. It will repay itself: I laready have a project on Lyng that suffers from implicit casts har to trace errors. +Compile time error unless it is an Object own method. Object members are always allowed even on unknown types; non-Object members require explicit casts. This avoids runtime lookup while keeping common Object APIs available. No runtime lookups or fallbacks: - All symbol and member resolution must be done at compile time.