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

@@ -7458,6 +7458,14 @@ class Compiler(
isOptional: Boolean,
explicitTypeArgs: List<TypeDecl>? = null
): ObjRef {
val thisTypeName = when (left) {
is LocalVarRef -> if (left.name == "this") currentImplicitThisTypeName() else null
is QualifiedThisRef -> left.typeName
else -> null
}
if (thisTypeName != null && codeContexts.any { it is CodeContext.ClassBody && it.name == thisTypeName }) {
throw ScriptError(cc.currentPos(), "constructor delegation through this is not supported; call the class by name")
}
var detectedBlockArgument = blockArgument
val expectedReceiver = tailBlockReceiverType(left)
val expectedTailCallable = expectedCallableArgumentType(left, 0)
@@ -9984,6 +9992,10 @@ class Compiler(
name = t.value
nameStartPos = t.pos
}
if (parentContext is CodeContext.ClassBody && extTypeName == null &&
name == parentContext.name.substringAfterLast('.')) {
throw ScriptError(nameStartPos, "member function cannot have the same name as its enclosing class: $name")
}
val extensionWrapperName = extTypeName?.let { extensionCallableName(it, name) }
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
var memberMethodId = if (extTypeName == null) classCtx?.memberMethodIds?.get(name) else null
@@ -0,0 +1,62 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.eval
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ClassNameMemberRegressionTest {
@Test
fun rejectsClassNamedMembersAtTheirDeclaration() = runTest {
for (member in listOf(
"fun Same(y): String = y.toString()",
"fun Same(y, z): String = y.toString() + z.toString()",
"fun Same(y): Same = Same(y * 2)",
"static fun Same(y): Same = Same(y)",
"fun Same(y): String = \"member\"; fun literal() = Same::class.toString()"
)) {
val error = assertFailsWith<ScriptError> {
eval("class Same(x) {\n $member\n}")
}
assertContains(error.errorMessage, "member function cannot have the same name as its enclosing class")
assertEquals(1, error.pos.line)
}
}
@Test
fun thisIsNotConstructorDelegation() = runTest {
for (receiver in listOf("this", "this@Factory")) {
val error = assertFailsWith<ScriptError> {
eval("class Factory(x) { fun again(): Factory = $receiver(x + 1) }")
}
assertContains(error.errorMessage, "constructor delegation through this is not supported")
}
}
@Test
fun constructorAndClassLiteralRemainAvailable() = runTest {
assertEquals("3", eval("""
class Factory(val x: Int) {
fun again(y: Int): Factory = Factory(y)
fun literal(): String = Factory::class.toString()
}
val f = Factory(1)
f.literal()
f.again(3).x.toString()
""".trimIndent()).toString())
}
@Test
fun localFunctionsCanStillShadowClassNames() = runTest {
assertEquals("local 7", eval("""
class Local(x) {
fun probe(y): String {
fun Local(z): String = "local " + z.toString()
return Local(y)
}
}
Local(1).probe(7)
""".trimIndent()).toString())
}
}