66 lines
4.0 KiB
Markdown
66 lines
4.0 KiB
Markdown
### Ticket 2 — Static member calls: declared return type is not propagated, receiver becomes `Object`
|
|
|
|
**Summary.** The **invocation** of a `static` member (class or `object` singleton) loses its declared return type at the call site; the result is treated as a general receiver, so every type-specific member access fails to compile. The runtime value is correct — this is purely a static-typing defect. Constructor calls, instance-method calls and top-level function calls all preserve the declared type correctly, so the inconsistency is specific to static members.
|
|
|
|
**Repro**
|
|
```lyng
|
|
class S(x) {
|
|
static fun make(y): S = S(y) // return type is explicitly declared
|
|
fun hi(): String = "hi"
|
|
}
|
|
|
|
val a: S = S.make(1) // OK — annotation rescues it
|
|
println(a.hi())
|
|
|
|
val b = S.make(2) // b is S == true at runtime
|
|
println(b.hi()) // Error: member access requires compile-time receiver type: hi
|
|
println(S.make(1).hi())// Error: same, chained call
|
|
println((S.make(3) as S).hi()) // OK — explicit cast rescues it
|
|
fun need(v: S) = v.hi(); need(S.make(4)) // OK — typed argument position rescues it
|
|
```
|
|
Not limited to self-returning factories — any declared return type is dropped:
|
|
```lyng
|
|
class K(x) { static fun text(y): String = "t"; static fun num(y): Int = y }
|
|
val s = K.text(1); println(s.length) // Error: ... compile-time receiver type: length
|
|
val n = K.num(1); println(n + 1) // same class of failure
|
|
object O { static fun text(y): String = "o" }
|
|
val o = O.text(1); println(o.length) // Error: ... on O.text result
|
|
class B(x) { static fun noRet(y) = B(y) } // untyped static: also general
|
|
class Box<T>(val value: T) { static fun of<T>(v: T): Box<T> = Box(v) }
|
|
val bx = Box.of(42); println(bx.value) // Error; val bx2: Box<Int> = Box.of(43) works
|
|
```
|
|
Controls that **do** work correctly: `val d = K8(9); d.hi()` (constructor), `val c = S(3).again(4); c.hi()` (instance method), `val t = topLevelMake(1); t.x` (top-level function).
|
|
|
|
**Expected.** `StaticClass.member(args)` must produce the declared return type as the static compile-time receiver type, exactly like instance and top-level calls. Only genuinely untyped statics (`fun` without a return type and uninferrable bodies) should fall back to `Object`/`Obj`.
|
|
|
|
**Suggested tests.** Matrix over `{constructor | instance method | top-level fun | static fun} × {declared ret type | inferred | omitted}` asserting that `.length`, arithmetic, `is T`-then-member, chained calls and generic instantiation behave identically for statics as for the other three; plus `object` singletons and generic statics.
|
|
|
|
|
|
|
|
## Resolution
|
|
|
|
Valid: static method signatures were excluded from the compiler's member type
|
|
metadata, both before body compilation and when inferred returns were recorded.
|
|
They now use the same callable signature and generic substitution path as
|
|
instance methods. Explicit annotations, inferred expression returns, primitive
|
|
returns, chained calls, and inferred/explicit generic arguments are covered by
|
|
`StaticReturnTypeRegressionTest`.
|
|
|
|
The singleton example additionally exposed runtime bugs once typing was
|
|
corrected: object static initializers were discarded, and a static call on an
|
|
object was sent to instance-method dispatch. Object declarations now preserve
|
|
and execute their static initialization statements before constructing the
|
|
singleton instance.
|
|
The compiler now retains its class-scope call decision for bytecode generation;
|
|
singleton static calls use the object's class and the existing class-scope
|
|
access opcode. Instance methods retain their dispatch path. Tests also cover
|
|
static calls with a trailing lambda.
|
|
|
|
Before the fix, all 5 original regression cases failed with missing compile-time
|
|
receiver types. The initial type-only correction passed 4, with the singleton
|
|
case then failing at runtime; dispatch is corrected in this same report.
|
|
|
|
Validation: all 6 focused JVM regression tests passed, followed by all 1,040
|
|
`:lynglib:jvmTest` tests (benchmarks excluded by the repository default).
|
|
`git diff --check` also passed.
|