Reject class-named members and unsupported constructor delegation
This commit is contained in:
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())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user