Allow Object members on unknown types

This commit is contained in:
Sergey Chernov 2026-02-04 03:40:07 +03:00
parent 47654aee38
commit 1fab2702dd
7 changed files with 34 additions and 35 deletions

View File

@ -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. - 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. - 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. - Nullability is Kotlin-style: `T` non-null, `T?` nullable, `!!` asserts non-null.
- `void` is a singleton of class `Void` (syntax sugar for return type). - `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. - 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.

View File

@ -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). - **Equality**: `==` (equals), `!=` (not equals), `===` (ref identity), `!==` (ref not identity).
- **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1). - **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1).
- **Destructuring**: `val [a, b, rest...] = list`. Supports nested `[a, [b, c]]` and splats. - **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) ## 2. Object-Oriented Programming (OOP)
- **Multiple Inheritance**: Supported with **C3 MRO** (Python-style). Diamond-safe. - **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. - **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. - **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. - **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<Object>`, empty map is `{:}`.
- **Generics**: Bounds with `T: A & B` or `T: A | B`; variance uses `out`/`in`.
## 3. Delegation (`by`) ## 3. Delegation (`by`)
Unified model for `val`, `var`, and `fun`. 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` ). - **Collections**: `List` ( `[a, b]` ), `Map` ( `Map(k => v)` ), `Set` ( `Set(a, b)` ). `MapEntry` ( `k => v` ).
## 5. Patterns & Shorthands ## 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:)`. - **Named Arguments**: `fun(y: 10, x: 5)`. Shorthand: `Point(x:, y:)`.
- **Varargs & Splats**: `fun f(args...)`, `f(...otherList)`. - **Varargs & Splats**: `fun f(args...)`, `f(...otherList)`.
- **Labels**: `loop@ for(x in list) { if(x == 0) break@loop }`. - **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 ## 6. Operators & Methods to Overload
| Op | Method | Op | Method | | Op | Method | Op | Method |

View File

@ -2027,10 +2027,14 @@ class BytecodeCompiler(
private fun compileFieldRef(ref: FieldRef): CompiledValue? { private fun compileFieldRef(ref: FieldRef): CompiledValue? {
val receiverClass = resolveReceiverClass(ref.target) val receiverClass = resolveReceiverClass(ref.target)
?: throw BytecodeCompileException( ?: if (isAllowedObjectMember(ref.name)) {
"Member access requires compile-time receiver type: ${ref.name}", Obj.rootObjectType
Pos.builtIn } else {
) throw BytecodeCompileException(
"Member access requires compile-time receiver type: ${ref.name}",
Pos.builtIn
)
}
val fieldId = receiverClass.instanceFieldIdMap()[ref.name] val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null

View File

@ -291,7 +291,7 @@ override fun List<T>.toString() {
var result = "[" var result = "["
for (item in this) { for (item in this) {
if (!first) result += "," if (!first) result += ","
result += (item as Object).toString() result += item.toString()
first = false first = false
} }
result + "]" result + "]"
@ -311,7 +311,7 @@ fun List<T>.sort(): Void {
fun Exception.printStackTrace(): Void { fun Exception.printStackTrace(): Void {
println(this) println(this)
for( entry in stackTrace ) { for( entry in stackTrace ) {
println("\tat "+(entry as Object).toString()) println("\tat "+entry.toString())
} }
} }

View File

@ -6,32 +6,16 @@ Module focus: :lynglib
Current focus Current focus
- Enforce compile-time name/member resolution only; no runtime scope lookup or fallback. - 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). - 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 Key recent changes
- Added memberId storage and lookup on ObjClass/ObjInstance; compiler emits memberId ops. - Removed method callsite PICs and fallback opcodes; bytecode now relies on compile-time member ids only.
- Removed GET_THIS_MEMBER/SET_THIS_MEMBER usage from bytecode. - Operator dispatch emits memberId calls when known; falls back to Obj opcodes for allowed built-ins without name lookup.
- Added ObjIterable.iterator() abstract method to ensure methodId exists. - Object members are allowed on unknown types; other members still require a statically known receiver type.
- Bytecode compiler now throws if receiver type is unknown for member calls. - Renamed BytecodeFallbackException to BytecodeCompileException.
- 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).
Known failing test Known failing tests
- ScriptTest.testForInIterableUnknownTypeDisasm (jvmTest) - None (jvmTest passing).
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.
Files touched recently Files touched recently
- notes/type_system_spec.md (spec updated) - notes/type_system_spec.md (spec updated)

View File

@ -1,10 +1,11 @@
# Bytecode call-site PIC + fallback gating # Bytecode call-site PIC + fallback gating (obsolete)
Changes Changes
- Added method call PIC path in bytecode VM with new CALL_SLOT/CALL_VIRTUAL opcodes. - 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. - Fixed FieldRef property/delegate resolution to avoid bypassing ObjRecord delegation.
- Prevent delegated ObjRecord mutation by returning a resolved copy. - 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. - 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 Rationale
- Fixes JVM test regressions and avoids premature evaluation of Statement args. - Fixes JVM test regressions and avoids premature evaluation of Statement args.

View File

@ -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? - 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: No runtime lookups or fallbacks:
- All symbol and member resolution must be done at compile time. - All symbol and member resolution must be done at compile time.