Reject class-named members and unsupported constructor delegation

This commit is contained in:
sergeych committed 2026-10-20 23:57:41 +04:00
1 parent 26a1ca9710
commit 04c9343a9d
5 files changed
+155

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
### Ticket 1 — Name resolution: a member function named like its class silently shadows the primary constructor inside the class body
**Summary.** `class` bodies accept a member function whose name equals the class name. That member then wins name resolution for **all unqualified calls inside the class body**, including `init` blocks, method bodies and nested lambdas. The primary constructor becomes unreachable from inside the class, with no escape hatch. Compiles cleanly, breaks at runtime.
**Repro**
```lyng
class C1(x) {
fun C1(y): String = "member " + y.toString()
init { println(C1(9)) } // -> "member C1(9)", NOT the constructor
}
class C2(x) {
fun C2(y, z): String = y.toString() + z.toString()
fun probe(): String = C2(7, 8) // member chosen although ctor takes 1 arg
}
class Fact(x) {
fun Fact(y): Fact = Fact(y * 2) // compiles; unqualified Fact(...) = itself
}
Fact(1).Fact(3) // java.lang.StackOverflowError
```
Local functions shadow too:
```lyng
class M3(x) {
fun probe(y): String {
fun M3(z): String = "local " + z.toString()
return M3(y) // local member wins, ctor unreachable
}
}
```
No workaround found:
```lyng
class Q1(x) {
fun Q1(y): String = "member"
fun a(): String = Q1.Q1(3) // IllegalArgumentException: expected 1 arguments, got 0
fun b(): String = (Q1(5) as Q1).Q1(6) // ClassCastException: Cannot cast String to Q1
}
class G1(x) { fun g(): G1 = this(x + 1) } // Exception: operation is not implemented
```
Related symptom: adding `Type::class` in a class that also has a same-name member turns the **declaration** itself into a hard error pointing at the wrong place:
```lyng
class D16(x) {
fun D16(y): String = "member " + y.toString() // Error: expected 1 arguments, got 0
fun lit(): String = D16::class.toString()
}
```
Control cases compile fine: same-name member alone ✔, `::class` alone ✔, the combination ✘.
**Expected.** Either (a) reject a member whose name collides with the class name at compile time (preferred — it is never a constructor in Lyng, there are no secondary constructors), or (b) keep constructor calls resolvable for the primary arity and fall back to members only for non-matching arity, plus a working `this(...)`/`this@Type(...)` delegation. In any case `Type::class` must not be affected by a same-name member, and diagnostics must point at the actual construct.
**Suggested tests.** `fun SameName(...)` declared in a class with the same name → expected compile error; self-recursive factory → must not reach runtime stack overflow; `Type::class` inside such a class → must compile; `this(...)` → either implement or emit a clear compile-time error instead of `operation is not implemented`.
## Resolution
Valid: direct instance/static member functions named after their enclosing class
are now rejected at the function name. This also rejects the collision before
any `::class` expression can produce a misleading downstream diagnostic.
The supported control is `Type::class` in a class without the illegal member;
the suggested test asking the illegal declaration to compile contradicts the
report's preferred resolution.
Direct `this(...)` / `this@Type(...)` calls on the enclosing class instance now
report unsupported constructor delegation during compilation. Use `Type(...)`
to construct another instance; no secondary constructors were introduced.
Local functions shadowing class names are ordinary lexical shadowing, not a bug,
and remain supported. Regression coverage: `ClassNameMemberRegressionTest`.
Before the fix, 2 of its 4 tests failed; the constructor/class-literal and local
shadowing controls passed.
Validation: all 4 focused JVM regression tests passed, followed by the full
`:lynglib:jvmTest` suite (benchmarks excluded by the repository default).