Allow Object members on unknown types
This commit is contained in:
parent
47654aee38
commit
1fab2702dd
@ -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.
|
||||||
|
|||||||
@ -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 |
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user