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:
parent
e7c1adb2c5
commit
d82302dd01
@ -34,6 +34,11 @@ Valid examples:
|
||||
|
||||
Ellipsis are used to declare variadic arguments. It basically means "all the arguments available here". It means, ellipsis argument could be in any part of the list, being, end or middle, but there could be only one ellipsis argument and it must not have default value, its default value is always `[]`, en empty list.
|
||||
|
||||
Ellipsis can also appear in **function types** to denote a variadic position:
|
||||
|
||||
var f: (Int, Object..., String)->Real
|
||||
var anyArgs: (...)->Int // shorthand for (Object...)->Int
|
||||
|
||||
Ellipsis argument receives what is left from arguments after processing regular one that could be before or after.
|
||||
|
||||
Ellipsis could be a first argument:
|
||||
|
||||
@ -225,6 +225,45 @@ Notes:
|
||||
- Use [LyngClassBridge] to bind by name/module, or by an already resolved `ObjClass`.
|
||||
- Use `ObjInstance.data` / `ObjClass.classData` to attach Kotlin‑side state when needed.
|
||||
|
||||
### 6.5a) Bind Kotlin implementations to declared Lyng objects
|
||||
|
||||
For `extern object` declarations, bind implementations to the singleton instance using `ModuleScope.bindObject`.
|
||||
This mirrors class binding but targets an already created object instance.
|
||||
|
||||
```lyng
|
||||
// Lyng side (in a module)
|
||||
extern object HostObject {
|
||||
extern fun add(a: Int, b: Int): Int
|
||||
extern val status: String
|
||||
extern var count: Int
|
||||
}
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// Kotlin side (binding)
|
||||
val moduleScope = importManager.createModuleScope(Pos.builtIn, "bridge.obj")
|
||||
moduleScope.bindObject("HostObject") {
|
||||
classData = "OK"
|
||||
init { _ -> data = 0L }
|
||||
addFun("add") { _, _, args ->
|
||||
val a = args.requiredArg<ObjInt>(0).value
|
||||
val b = args.requiredArg<ObjInt>(1).value
|
||||
ObjInt.of(a + b)
|
||||
}
|
||||
addVal("status") { _, _ -> ObjString(classData as String) }
|
||||
addVar(
|
||||
"count",
|
||||
get = { _, inst -> ObjInt.of((inst as ObjInstance).data as Long) },
|
||||
set = { _, inst, value -> (inst as ObjInstance).data = (value as ObjInt).value }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Members must be marked `extern` so the compiler emits ABI slots for Kotlin bindings.
|
||||
- You can also bind by name/module via `LyngObjectBridge.bind(...)`.
|
||||
|
||||
### 6.6) Preferred: Kotlin reflection bridge for call‑by‑name
|
||||
|
||||
For Kotlin code that needs dynamic access to Lyng variables, functions, or members, use the bridge resolver.
|
||||
|
||||
@ -518,6 +518,13 @@ Examples:
|
||||
fun inc(x=0) = x + 1 // (Int)->Int
|
||||
fun maybe(flag) { if(flag) 1 else null } // ()->Int?
|
||||
|
||||
Function types are written as `(T1, T2, ...)->R`. You can include ellipsis in function *types* to
|
||||
express a variadic position:
|
||||
|
||||
var fmt: (String, Object...)->String
|
||||
var f: (Int, Object..., String)->Real
|
||||
var anyArgs: (...)->Int // shorthand for (Object...)->Int
|
||||
|
||||
Untyped locals are allowed, but their type is fixed on the first assignment:
|
||||
|
||||
var x
|
||||
@ -735,6 +742,11 @@ one could be with ellipsis that means "the rest pf arguments as List":
|
||||
assert( { a, b...-> [a,...b] }(100, 1, 2, 3) == [100, 1, 2, 3])
|
||||
void
|
||||
|
||||
Type-annotated lambdas can use variadic *function types* as well:
|
||||
|
||||
val f: (Int, Object..., String)->Real = { a, rest..., b -> 0.0 }
|
||||
val anyArgs: (...)->Int = { -> 0 }
|
||||
|
||||
### Using lambda as the parameter
|
||||
|
||||
See also: [Testing and Assertions](Testing.md)
|
||||
|
||||
@ -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) {
|
||||
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()
|
||||
params += paramDecl to paramMini
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user