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:
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())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user