Add support for variadic function types and ellipsis in type declarations, stricter lamda types compile-time checks and object binding for Kotlin bridges

This commit is contained in:
sergeych committed 2026-02-19 11:12:39 +03:00
1 parent e7c1adb2c5
commit d82302dd01
15 files changed
+387 -12

No files matched your search

@@ -209,6 +209,9 @@ class Compiler(
if (plan.slots.containsKey(name)) continue
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
scopeSeedNames.add(name)
if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl
}
val instance = record.value as? ObjInstance
if (instance != null && nameObjClass[name] == null) {
nameObjClass[name] = instance.objClass
@@ -285,6 +288,12 @@ class Compiler(
record.type == ObjRecord.Type.Delegated
)
scopeSeedNames.add(name)
if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl
}
if (record.typeDecl != null) {
slotTypeDeclByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = record.typeDecl
}
}
}
@@ -843,6 +852,7 @@ class Compiler(
visibility = Visibility.Public,
initializer = initStmt,
isTransient = false,
typeDecl = null,
slotIndex = slotIndex,
scopeId = scopeId,
startPos = nameToken.pos,
@@ -1180,6 +1190,25 @@ class Compiler(
}
}
private fun seedNameTypeDeclFromScope(scope: Scope) {
var current: Scope? = scope
while (current != null) {
for ((name, record) in current.objects) {
if (!record.visibility.isPublic) continue
if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl
}
}
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
val record = current.getSlotRecord(slotIndex)
if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl
}
}
current = current.parent
}
}
private fun resolveImportBinding(name: String, pos: Pos): ImportBindingResolution? {
val seedRecord = findSeedScopeRecord(name)?.takeIf { it.visibility.isPublic }
val rootRecord = importManager.rootScope.objects[name]?.takeIf { it.visibility.isPublic }
@@ -1387,6 +1416,7 @@ class Compiler(
returnType = transform(decl.returnType),
nullable = decl.isNullable
)
is TypeDecl.Ellipsis -> TypeDecl.Ellipsis(transform(decl.elementType), decl.isNullable)
is TypeDecl.Union -> TypeDecl.Union(decl.options.map { transform(it) }, decl.isNullable)
is TypeDecl.Intersection -> TypeDecl.Intersection(decl.options.map { transform(it) }, decl.isNullable)
else -> decl
@@ -1515,6 +1545,7 @@ class Compiler(
declareSlotNameIn(plan, "$~", isMutable = true, isDelegated = false)
}
seedScope?.let { seedNameObjClassFromScope(it) }
seedScope?.let { seedNameTypeDeclFromScope(it) }
seedNameObjClassFromScope(importManager.rootScope)
if (shouldSeedDefaultStdlib()) {
val stdlib = importManager.prepareImport(start, "lyng.stdlib", null)
@@ -2211,6 +2242,7 @@ class Compiler(
stmt.visibility,
init,
stmt.isTransient,
stmt.typeDecl,
stmt.slotIndex,
stmt.scopeId,
stmt.pos,
@@ -3475,6 +3507,10 @@ class Compiler(
val ret = expandTypeAliases(type.returnType, pos, seen)
TypeDecl.Function(receiver, params, ret, type.nullable)
}
is TypeDecl.Ellipsis -> {
val elem = expandTypeAliases(type.elementType, pos, seen)
TypeDecl.Ellipsis(elem, type.nullable)
}
is TypeDecl.Union -> {
val options = type.options.map { expandTypeAliases(it, pos, seen) }
TypeDecl.Union(options, type.isNullable)
@@ -3560,6 +3596,10 @@ class Compiler(
val ret = substituteTypeAliasTypeVars(type.returnType, bindings)
TypeDecl.Function(receiver, params, ret, type.nullable)
}
is TypeDecl.Ellipsis -> {
val elem = substituteTypeAliasTypeVars(type.elementType, bindings)
TypeDecl.Ellipsis(elem, type.nullable)
}
is TypeDecl.Union -> {
val options = type.options.map { substituteTypeAliasTypeVars(it, bindings) }
TypeDecl.Union(options, type.isNullable)
@@ -3578,6 +3618,7 @@ class Compiler(
TypeDecl.TypeAny -> TypeDecl.TypeNullableAny
TypeDecl.TypeNullableAny -> type
is TypeDecl.Function -> type.copy(nullable = true)
is TypeDecl.Ellipsis -> type.copy(nullable = true)
is TypeDecl.TypeVar -> type.copy(nullable = true)
is TypeDecl.Union -> type.copy(nullable = true)
is TypeDecl.Intersection -> type.copy(nullable = true)
@@ -3634,14 +3675,41 @@ class Compiler(
fun parseParamTypes(): List<Pair<TypeDecl, MiniTypeRef>> {
val params = mutableListOf<Pair<TypeDecl, MiniTypeRef>>()
var seenEllipsis = false
cc.skipWsTokens()
if (cc.peekNextNonWhitespace().type == Token.Type.RPAREN) {
cc.nextNonWhitespace()
return params
}
while (true) {
val (paramDecl, paramMini) = parseTypeExpressionWithMini()
params += paramDecl to paramMini
cc.skipWsTokens()
val next = cc.peekNextNonWhitespace()
if (next.type == Token.Type.ELLIPSIS) {
val ell = cc.nextNonWhitespace()
if (seenEllipsis) {
ell.raiseSyntax("function type can contain only one ellipsis")
}
seenEllipsis = true
val paramDecl = TypeDecl.Ellipsis(TypeDecl.TypeAny)
val mini = MiniTypeName(
MiniRange(ell.pos, ell.pos),
listOf(MiniTypeName.Segment("Object", MiniRange(ell.pos, ell.pos))),
nullable = false
)
params += paramDecl to mini
} else {
val (paramDecl, paramMini) = parseTypeExpressionWithMini()
val finalDecl = if (cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)) {
if (seenEllipsis) {
cc.current().raiseSyntax("function type can contain only one ellipsis")
}
seenEllipsis = true
TypeDecl.Ellipsis(paramDecl)
} else {
paramDecl
}
params += finalDecl to paramMini
}
val sep = cc.nextNonWhitespace()
when (sep.type) {
Token.Type.COMMA -> continue
@@ -3952,6 +4020,7 @@ class Compiler(
is TypeDecl.Simple -> typeDecl.name
is TypeDecl.Generic -> typeDecl.name
is TypeDecl.Function -> "Callable"
is TypeDecl.Ellipsis -> "${typeDeclName(typeDecl.elementType)}..."
is TypeDecl.TypeVar -> typeDecl.name
is TypeDecl.Union -> typeDecl.options.joinToString(" | ") { typeDeclName(it) }
is TypeDecl.Intersection -> typeDecl.options.joinToString(" & ") { typeDeclName(it) }
@@ -4129,6 +4198,7 @@ class Compiler(
val nullable = type.isNullable
val base = if (!nullable) type else when (type) {
is TypeDecl.Function -> type.copy(nullable = false)
is TypeDecl.Ellipsis -> type.copy(nullable = false)
is TypeDecl.TypeVar -> type.copy(nullable = false)
is TypeDecl.Union -> type.copy(nullable = false)
is TypeDecl.Intersection -> type.copy(nullable = false)
@@ -4145,6 +4215,7 @@ class Compiler(
is TypeDecl.Simple -> "S:${type.name}"
is TypeDecl.Generic -> "G:${type.name}<${type.args.joinToString(",") { typeDeclKey(it) }}>"
is TypeDecl.Function -> "F:(${type.params.joinToString(",") { typeDeclKey(it) }})->${typeDeclKey(type.returnType)}"
is TypeDecl.Ellipsis -> "E:${typeDeclKey(type.elementType)}"
is TypeDecl.TypeVar -> "V:${type.name}"
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
is TypeDecl.Intersection -> "I:${type.options.joinToString("&") { typeDeclKey(it) }}"
@@ -4671,6 +4742,144 @@ class Compiler(
}
}
private fun checkFunctionTypeCallArity(
target: ObjRef,
args: List<ParsedArgument>,
pos: Pos
) {
val decl = (resolveReceiverTypeDecl(target) as? TypeDecl.Function)
?: seedTypeDeclFromRef(target) as? TypeDecl.Function
?: return
if (args.any { it.isSplat }) return
val actual = args.size
val receiverCount = if (decl.receiver != null) 1 else 0
val paramList = mutableListOf<TypeDecl>()
decl.receiver?.let { paramList += it }
paramList += decl.params
val ellipsisIndex = paramList.indexOfFirst { it is TypeDecl.Ellipsis }
if (ellipsisIndex < 0) {
val expected = paramList.size
if (actual != expected) {
throw ScriptError(pos, "expected $expected arguments, got $actual")
}
return
}
val headCount = ellipsisIndex
val tailCount = paramList.size - ellipsisIndex - 1
val minArgs = headCount + tailCount
if (actual < minArgs) {
throw ScriptError(pos, "expected at least $minArgs arguments, got $actual")
}
}
private fun seedTypeDeclFromRef(ref: ObjRef): TypeDecl? {
val name = when (ref) {
is LocalVarRef -> ref.name
is LocalSlotRef -> ref.name
is FastLocalVarRef -> ref.name
else -> null
} ?: return null
seedScope?.getLocalRecordDirect(name)?.typeDecl?.let { return it }
return seedScope?.get(name)?.typeDecl
}
private fun checkFunctionTypeCallTypes(
target: ObjRef,
args: List<ParsedArgument>,
pos: Pos
) {
val decl = (resolveReceiverTypeDecl(target) as? TypeDecl.Function)
?: seedTypeDeclFromRef(target) as? TypeDecl.Function
?: return
val paramList = mutableListOf<TypeDecl>()
decl.receiver?.let { paramList += it }
paramList += decl.params
if (paramList.isEmpty()) return
val ellipsisIndex = paramList.indexOfFirst { it is TypeDecl.Ellipsis }
fun argTypeDecl(arg: ParsedArgument): TypeDecl? {
val stmt = arg.value as? ExpressionStatement ?: return null
val ref = stmt.ref
return inferTypeDeclFromRef(ref)
?: inferObjClassFromRef(ref)?.let { TypeDecl.Simple(it.className, false) }
}
fun typeDeclSubtypeOf(arg: TypeDecl, param: TypeDecl): Boolean {
if (param == TypeDecl.TypeAny || param == TypeDecl.TypeNullableAny) return true
val (argBase, argNullable) = stripNullable(arg)
val (paramBase, paramNullable) = stripNullable(param)
if (argNullable && !paramNullable) return false
if (paramBase == TypeDecl.TypeAny) return true
if (paramBase is TypeDecl.TypeVar) return true
if (argBase is TypeDecl.TypeVar) return true
if (paramBase is TypeDecl.Simple && (paramBase.name == "Object" || paramBase.name == "Obj")) return true
if (argBase is TypeDecl.Ellipsis) return typeDeclSubtypeOf(argBase.elementType, paramBase)
if (paramBase is TypeDecl.Ellipsis) return typeDeclSubtypeOf(argBase, paramBase.elementType)
return when (argBase) {
is TypeDecl.Union -> argBase.options.all { typeDeclSubtypeOf(it, paramBase) }
is TypeDecl.Intersection -> argBase.options.any { typeDeclSubtypeOf(it, paramBase) }
else -> when (paramBase) {
is TypeDecl.Union -> paramBase.options.any { typeDeclSubtypeOf(argBase, it) }
is TypeDecl.Intersection -> paramBase.options.all { typeDeclSubtypeOf(argBase, it) }
else -> {
val argClass = resolveTypeDeclObjClass(argBase) ?: return false
val paramClass = resolveTypeDeclObjClass(paramBase) ?: return false
argClass == paramClass || argClass.allParentsSet.contains(paramClass)
}
}
}
}
fun fail(argPos: Pos, expected: TypeDecl, got: TypeDecl) {
throw ScriptError(argPos, "argument type ${typeDeclName(got)} does not match ${typeDeclName(expected)}")
}
if (ellipsisIndex < 0) {
val limit = minOf(paramList.size, args.size)
for (i in 0 until limit) {
val arg = args[i]
val argType = argTypeDecl(arg) ?: continue
val paramType = paramList[i]
if (!typeDeclSubtypeOf(argType, paramType)) {
fail(arg.pos, paramType, argType)
}
}
return
}
val headCount = ellipsisIndex
val tailCount = paramList.size - ellipsisIndex - 1
val ellipsisType = paramList[ellipsisIndex] as TypeDecl.Ellipsis
val argCount = args.size
val headLimit = minOf(headCount, argCount)
for (i in 0 until headLimit) {
val arg = args[i]
val argType = argTypeDecl(arg) ?: continue
val paramType = paramList[i]
if (!typeDeclSubtypeOf(argType, paramType)) {
fail(arg.pos, paramType, argType)
}
}
val tailStartArg = maxOf(headCount, argCount - tailCount)
for (i in tailStartArg until argCount) {
val arg = args[i]
val paramType = paramList[paramList.size - (argCount - i)]
val argType = argTypeDecl(arg) ?: continue
if (!typeDeclSubtypeOf(argType, paramType)) {
fail(arg.pos, paramType, argType)
}
}
val ellipsisArgEnd = argCount - tailCount
for (i in headCount until ellipsisArgEnd) {
val arg = args[i]
val argType = if (arg.isSplat) {
val stmt = arg.value as? ExpressionStatement
val ref = stmt?.ref
ref?.let { inferElementTypeFromSpread(it) }
} else {
argTypeDecl(arg)
} ?: continue
if (!typeDeclSubtypeOf(argType, ellipsisType.elementType)) {
fail(arg.pos, ellipsisType.elementType, argType)
}
}
}
private fun collectTypeVarBindings(
paramType: TypeDecl,
argType: TypeDecl,
@@ -4739,10 +4948,11 @@ class Compiler(
TypeDecl.TypeAny, TypeDecl.TypeNullableAny -> true
is TypeDecl.Union -> argType.options.all { typeDeclSatisfiesBound(it, bound) }
is TypeDecl.Intersection -> argType.options.all { typeDeclSatisfiesBound(it, bound) }
is TypeDecl.Ellipsis -> typeDeclSatisfiesBound(argType.elementType, bound)
else -> when (bound) {
is TypeDecl.Union -> bound.options.any { typeDeclSatisfiesBound(argType, it) }
is TypeDecl.Intersection -> bound.options.all { typeDeclSatisfiesBound(argType, it) }
is TypeDecl.Simple, is TypeDecl.Generic, is TypeDecl.Function -> {
is TypeDecl.Simple, is TypeDecl.Generic, is TypeDecl.Function, is TypeDecl.Ellipsis -> {
val argClass = resolveTypeDeclObjClass(argType) ?: return false
val boundClass = resolveTypeDeclObjClass(bound) ?: return false
argClass == boundClass || argClass.allParentsSet.contains(boundClass)
@@ -5122,6 +5332,8 @@ class Compiler(
receiverTypeName
)
} else {
checkFunctionTypeCallArity(left, args, left.pos())
checkFunctionTypeCallTypes(left, args, left.pos())
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional)
}
@@ -5144,6 +5356,8 @@ class Compiler(
receiverTypeName
)
} else {
checkFunctionTypeCallArity(left, args, left.pos())
checkFunctionTypeCallTypes(left, args, left.pos())
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional)
}
@@ -7643,6 +7857,7 @@ class Compiler(
is TypeDecl.Simple -> type.name
is TypeDecl.Generic -> type.name
is TypeDecl.Function -> "Callable"
is TypeDecl.Ellipsis -> return resolveTypeDeclObjClass(type.elementType)
is TypeDecl.TypeVar -> return null
is TypeDecl.Union -> return null
is TypeDecl.Intersection -> return null
@@ -8206,12 +8421,18 @@ class Compiler(
}
nameObjClass[name] = initObjClass
}
val declaredType = if (varTypeDecl == TypeDecl.TypeAny || varTypeDecl == TypeDecl.TypeNullableAny) {
null
} else {
varTypeDecl
}
return VarDeclStatement(
name,
isMutable,
visibility,
initialExpression,
isTransient,
declaredType,
slotIndex,
scopeId,
start,
@@ -655,6 +655,7 @@ open class Scope(
isOverride: Boolean = false,
isTransient: Boolean = false,
callSignature: CallSignature? = null,
typeDecl: TypeDecl? = null,
fieldId: Int? = null,
methodId: Int? = null
): ObjRecord {
@@ -667,6 +668,7 @@ open class Scope(
isOverride = isOverride,
isTransient = isTransient,
callSignature = callSignature,
typeDecl = typeDecl,
memberName = name,
fieldId = fieldId,
methodId = methodId
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,10 @@ sealed class TypeDecl(val isNullable:Boolean = false) {
val returnType: TypeDecl,
val nullable: Boolean = false
) : TypeDecl(nullable)
data class Ellipsis(
val elementType: TypeDecl,
val nullable: Boolean = false
) : TypeDecl(nullable)
data class TypeVar(val name: String, val nullable: Boolean = false) : TypeDecl(nullable)
data class Union(val options: List<TypeDecl>, val nullable: Boolean = false) : TypeDecl(nullable)
data class Intersection(val options: List<TypeDecl>, val nullable: Boolean = false) : TypeDecl(nullable)
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng
@@ -25,6 +26,7 @@ class VarDeclStatement(
val visibility: Visibility,
val initializer: Statement?,
val isTransient: Boolean,
val typeDecl: TypeDecl?,
val slotIndex: Int?,
val scopeId: Int?,
private val startPos: Pos,
@@ -5730,7 +5730,8 @@ class BytecodeCompiler(
stmt.name,
stmt.isMutable,
stmt.visibility,
stmt.isTransient
stmt.isTransient,
stmt.typeDecl
)
)
builder.emit(Opcode.DECL_LOCAL, declId, localSlot)
@@ -5757,7 +5758,8 @@ class BytecodeCompiler(
stmt.name,
stmt.isMutable,
stmt.visibility,
stmt.isTransient
stmt.isTransient,
stmt.typeDecl
)
)
builder.emit(Opcode.DECL_LOCAL, declId, scopeSlot)
@@ -5775,7 +5777,8 @@ class BytecodeCompiler(
stmt.name,
stmt.isMutable,
stmt.visibility,
stmt.isTransient
stmt.isTransient,
stmt.typeDecl
)
)
builder.emit(Opcode.DECL_LOCAL, declId, value.slot)
@@ -1,5 +1,5 @@
/*
* Copyright 2026 Sergey S. Chernov
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,12 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.ArgsDeclaration
import net.sergeych.lyng.Pos
import net.sergeych.lyng.TypeDecl
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.ListLiteralRef
import net.sergeych.lyng.obj.Obj
@@ -68,6 +70,7 @@ sealed class BytecodeConst {
val isMutable: Boolean,
val visibility: Visibility,
val isTransient: Boolean,
val typeDecl: TypeDecl?,
) : BytecodeConst()
data class DelegatedDecl(
val name: String,
@@ -208,6 +208,7 @@ class BytecodeStatement private constructor(
stmt.visibility,
stmt.initializer?.let { unwrapDeep(it) },
stmt.isTransient,
stmt.typeDecl,
stmt.slotIndex,
stmt.scopeId,
stmt.pos,
@@ -2380,7 +2380,8 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
decl.isMutable,
decl.visibility,
isTransient = decl.isTransient,
type = ObjRecord.Type.Other
type = ObjRecord.Type.Other,
typeDecl = decl.typeDecl
)
)
return
@@ -2392,7 +2393,8 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
decl.isMutable,
decl.visibility,
isTransient = decl.isTransient,
type = ObjRecord.Type.Other
type = ObjRecord.Type.Other,
typeDecl = decl.typeDecl
)
val moduleScope = frame.scope as? ModuleScope
if (moduleScope != null) {
@@ -39,6 +39,7 @@ data class ObjRecord(
/** The receiver object to resolve this member against (for instance fields/methods). */
var receiver: Obj? = null,
val callSignature: net.sergeych.lyng.CallSignature? = null,
val typeDecl: net.sergeych.lyng.TypeDecl? = null,
val memberName: String? = null,
val fieldId: Int? = null,
val methodId: Int? = null,
@@ -1,5 +1,5 @@
/*
* Copyright 2026 Sergey S. Chernov
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
@@ -63,6 +64,7 @@ internal fun matchesTypeDecl(scope: Scope, value: Obj, typeDecl: TypeDecl): Bool
if (cls != null) value.isInstanceOf(cls) else value.isInstanceOf(typeDecl.name.substringAfterLast('.'))
}
is TypeDecl.Function -> value.isInstanceOf("Callable")
is TypeDecl.Ellipsis -> matchesTypeDecl(scope, value, typeDecl.elementType)
is TypeDecl.Union -> typeDecl.options.any { matchesTypeDecl(scope, value, it) }
is TypeDecl.Intersection -> typeDecl.options.all { matchesTypeDecl(scope, value, it) }
}
@@ -90,10 +92,11 @@ internal fun typeDeclIsSubtype(scope: Scope, left: TypeDecl, right: TypeDecl): B
return when (l) {
is TypeDecl.Union -> l.options.all { typeDeclIsSubtype(scope, it, r) }
is TypeDecl.Intersection -> l.options.any { typeDeclIsSubtype(scope, it, r) }
is TypeDecl.Ellipsis -> typeDeclIsSubtype(scope, l.elementType, r)
else -> when (r) {
is TypeDecl.Union -> r.options.any { typeDeclIsSubtype(scope, l, it) }
is TypeDecl.Intersection -> r.options.all { typeDeclIsSubtype(scope, l, it) }
is TypeDecl.Simple, is TypeDecl.Generic, is TypeDecl.Function -> {
is TypeDecl.Simple, is TypeDecl.Generic, is TypeDecl.Function, is TypeDecl.Ellipsis -> {
val leftClass = resolveTypeDeclClass(scope, l) ?: return false
val rightClass = resolveTypeDeclClass(scope, r) ?: return false
leftClass == rightClass || leftClass.allParentsSet.contains(rightClass)
@@ -171,6 +174,7 @@ private fun stripNullable(type: TypeDecl): TypeDecl {
} else {
when (type) {
is TypeDecl.Function -> type.copy(nullable = false)
is TypeDecl.Ellipsis -> type.copy(nullable = false)
is TypeDecl.TypeVar -> type.copy(nullable = false)
is TypeDecl.Union -> type.copy(nullable = false)
is TypeDecl.Intersection -> type.copy(nullable = false)
@@ -186,6 +190,7 @@ private fun makeNullable(type: TypeDecl): TypeDecl {
TypeDecl.TypeAny -> TypeDecl.TypeNullableAny
TypeDecl.TypeNullableAny -> type
is TypeDecl.Function -> type.copy(nullable = true)
is TypeDecl.Ellipsis -> type.copy(nullable = true)
is TypeDecl.TypeVar -> type.copy(nullable = true)
is TypeDecl.Union -> type.copy(nullable = true)
is TypeDecl.Intersection -> type.copy(nullable = true)
@@ -200,6 +205,7 @@ private fun typeDeclKey(type: TypeDecl): String = when (type) {
is TypeDecl.Simple -> "S:${type.name}"
is TypeDecl.Generic -> "G:${type.name}<${type.args.joinToString(",") { typeDeclKey(it) }}>"
is TypeDecl.Function -> "F:(${type.params.joinToString(",") { typeDeclKey(it) }})->${typeDeclKey(type.returnType)}"
is TypeDecl.Ellipsis -> "E:${typeDeclKey(type.elementType)}"
is TypeDecl.TypeVar -> "V:${type.name}"
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
is TypeDecl.Intersection -> "I:${type.options.joinToString("&") { typeDeclKey(it) }}"
@@ -216,6 +222,7 @@ private fun resolveTypeDeclClass(scope: Scope, type: TypeDecl): ObjClass? {
direct ?: scope[type.name.substringAfterLast('.')]?.value as? ObjClass
}
is TypeDecl.Function -> scope["Callable"]?.value as? ObjClass
is TypeDecl.Ellipsis -> resolveTypeDeclClass(scope, type.elementType)
is TypeDecl.TypeVar -> {
val bound = scope[type.name]?.value
when (bound) {
@@ -213,4 +213,18 @@ class BridgeBindingTest {
""".trimIndent()
)
}
// @Test
// fun testGlobalBindingsProperty() = runTest {
// eval("""
// val D: ()->Void = dynamic {
// get { name ->
// {
// args -> "name: "+name+" args="+args
// }
// }
// }
// assertEquals("name: foo args=[42,bar]", D.foo(42, "bar"))
// """.trimIndent())
// }
}
@@ -16,6 +16,8 @@
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.eval
import kotlin.test.Test
import kotlin.test.assertFailsWith
@@ -302,4 +304,61 @@ class TypesTest {
}
""")
}
@Test
fun testLambdaTypes1() = runTest {
val scope = Script.newScope()
// declare: ok
scope.eval("""
var l1: (Int,String)->String
""".trimIndent())
// this should be Lyng compile time exception
assertFailsWith<ScriptError> {
scope.eval("""
fun test() {
// compiler should detect that l1 us called with arguments that does not match
// declare type (Int,String)->String:
l1()
}
""".trimIndent())
}
}
@Test
fun testLambdaTypesEllipsis() = runTest {
val scope = Script.newScope()
scope.eval("""
var l2: (Int,Object...,String)->Real
var l4: (Int,String...,String)->Real
var l3: (...)->Int
""".trimIndent())
assertFailsWith<ScriptError> {
scope.eval("""
fun testTooFew() {
l2(1)
}
""".trimIndent())
}
assertFailsWith<ScriptError> {
scope.eval("""
fun testWrongHead() {
l2("x", "y")
}
""".trimIndent())
}
assertFailsWith<ScriptError> {
scope.eval("""
fun testWrongEllipsis() {
l4(1, 2, "x")
}
""".trimIndent())
}
scope.eval("""
fun testOk1() { l2(1, "x") }
fun testOk2() { l2(1, 2, 3, "x") }
fun testOk3() { l3() }
fun testOk4() { l3(1, true, "x") }
fun testOk5() { l4(1, "a", "b", "x") }
""".trimIndent())
}
}