Add minimal generics and typed callable casts

This commit is contained in:
Sergey Chernov 2026-02-03 07:36:09 +03:00
parent 51b397686d
commit c9da0b256f
9 changed files with 419 additions and 45 deletions

View File

@ -22,7 +22,8 @@ sealed class CodeContext {
class Function(
val name: String,
val implicitThisMembers: Boolean = false,
val implicitThisTypeName: String? = null
val implicitThisTypeName: String? = null,
val typeParams: Set<String> = emptySet()
): CodeContext()
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
val pendingInitializations = mutableMapOf<String, Pos>()

View File

@ -103,6 +103,8 @@ class Compiler(
}
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
val plan = moduleSlotPlan() ?: return
@ -112,6 +114,36 @@ class Compiler(
if (!record.visibility.isPublic) continue
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
}
for ((cls, map) in current.extensions) {
for ((name, record) in map) {
if (!record.visibility.isPublic) continue
when (record.type) {
ObjRecord.Type.Property -> {
declareSlotNameIn(
plan,
extensionPropertyGetterName(cls.className, name),
isMutable = false,
isDelegated = false
)
val prop = record.value as? ObjProperty
if (prop?.setter != null) {
declareSlotNameIn(
plan,
extensionPropertySetterName(cls.className, name),
isMutable = false,
isDelegated = false
)
}
}
else -> declareSlotNameIn(
plan,
extensionCallableName(cls.className, name),
isMutable = false,
isDelegated = false
)
}
}
}
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
val record = current.getSlotRecord(slotIndex)
if (!record.visibility.isPublic) continue
@ -126,6 +158,8 @@ class Compiler(
val plan = moduleSlotPlan() ?: return
val saved = cc.savePos()
var depth = 0
var parenDepth = 0
var bracketDepth = 0
fun nextNonWs(): Token {
var t = cc.next()
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
@ -139,7 +173,12 @@ class Compiler(
when (t.type) {
Token.Type.LBRACE -> depth++
Token.Type.RBRACE -> if (depth > 0) depth--
Token.Type.LPAREN -> parenDepth++
Token.Type.RPAREN -> if (parenDepth > 0) parenDepth--
Token.Type.LBRACKET -> bracketDepth++
Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth--
Token.Type.ID -> if (depth == 0) {
if (parenDepth > 0 || bracketDepth > 0) continue
when (t.value) {
"fun", "fn" -> {
val nameToken = nextNonWs()
@ -399,6 +438,14 @@ class Compiler(
return null
}
private fun currentTypeParams(): Set<String> {
for (ctx in codeContexts.asReversed()) {
val fn = ctx as? CodeContext.Function ?: continue
if (fn.typeParams.isNotEmpty()) return fn.typeParams
}
return emptySet()
}
private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? {
for (i in slotPlanStack.indices.reversed()) {
if (!includeModule && i == 0) continue
@ -468,6 +515,15 @@ class Compiler(
val ids = resolveMemberIds(name, pos, null)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
}
val implicitThisMembers = codeContexts.any { ctx ->
(ctx as? CodeContext.Function)?.implicitThisMembers == true
}
val implicitType = if (implicitThisMembers) currentImplicitThisTypeName() else null
if (implicitType != null && hasImplicitThisMember(name, implicitType)) {
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
}
val modulePlan = moduleSlotPlan()
val moduleEntry = modulePlan?.slots?.get(name)
if (moduleEntry != null) {
@ -514,15 +570,6 @@ class Compiler(
return ref
}
}
val implicitThis = codeContexts.any { ctx ->
(ctx as? CodeContext.Function)?.implicitThisMembers == true
}
if (implicitThis) {
val implicitType = currentImplicitThisTypeName()
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
}
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
if (classContext && extensionNames.contains(name)) {
resolutionSink?.referenceMember(name, pos)
@ -915,9 +962,16 @@ class Compiler(
private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds {
if (implicitTypeName == null) return resolveMemberIds(name, pos, null)
val info = resolveCompileClassInfo(implicitTypeName)
?: if (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $implicitTypeName")
val fieldId = info.fieldIds[name]
val methodId = info.methodIds[name]
val fieldId = info?.fieldIds?.get(name)
val methodId = info?.methodIds?.get(name)
if (fieldId == null && methodId == null) {
val cls = resolveClassByName(implicitTypeName)
if (cls != null) {
val fId = cls.instanceFieldIdMap()[name]
val mId = cls.instanceMethodIdMap(includeAbstract = true)[name]
if (fId != null || mId != null) return MemberIds(fId, mId)
}
}
if (fieldId == null && methodId == null) {
if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null)
if (allowUnresolvedRefs) return MemberIds(null, null)
@ -925,6 +979,19 @@ class Compiler(
}
return MemberIds(fieldId, methodId)
}
private fun hasImplicitThisMember(name: String, implicitTypeName: String?): Boolean {
if (implicitTypeName == null) return false
val info = resolveCompileClassInfo(implicitTypeName)
if (info != null && (info.fieldIds.containsKey(name) || info.methodIds.containsKey(name))) return true
val cls = resolveClassByName(implicitTypeName)
if (cls != null) {
if (cls.instanceFieldIdMap().containsKey(name) || cls.instanceMethodIdMap(includeAbstract = true).containsKey(name)) {
return true
}
}
return hasExtensionFor(implicitTypeName, name)
}
private val currentRangeParamNames: Set<String>
get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
private val capturePlanStack = mutableListOf<CapturePlan>()
@ -1443,10 +1510,19 @@ class Compiler(
break
}
val rvalue = parseExpressionLevel(level + 1)
?: throw ScriptError(opToken.pos, "Expecting expression")
val res = op.generate(opToken.pos, lvalue!!, rvalue)
val res = if (opToken.type == Token.Type.AS || opToken.type == Token.Type.ASNULL) {
val (typeDecl, _) = parseTypeExpressionWithMini()
val typeRef = typeDeclToTypeRef(typeDecl, opToken.pos)
if (opToken.type == Token.Type.AS) {
CastRef(lvalue!!, typeRef, false, opToken.pos)
} else {
CastRef(lvalue!!, typeRef, true, opToken.pos)
}
} else {
val rvalue = parseExpressionLevel(level + 1)
?: throw ScriptError(opToken.pos, "Expecting expression")
op.generate(opToken.pos, lvalue!!, rvalue)
}
if (opToken.type == Token.Type.ASSIGN) {
val ctx = codeContexts.lastOrNull()
if (ctx is CodeContext.ClassBody) {
@ -1527,7 +1603,10 @@ class Compiler(
Token.Type.LPAREN -> {
cc.next()
// instance method call
val parsed = parseArgs()
val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val parsed = parseArgs(receiverType)
val args = parsed.first
val tailBlock = parsed.second
if (left is LocalVarRef && left.name == "scope") {
@ -1578,7 +1657,10 @@ class Compiler(
// single lambda arg, like assertThrows { ... }
cc.next()
isCall = true
val lambda = parseLambdaExpression()
val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val lambda = parseLambdaExpression(receiverType)
val argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
operand = when (left) {
@ -2274,6 +2356,89 @@ class Compiler(
}
private fun parseTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
parseFunctionTypeWithMini()?.let { return it }
return parseSimpleTypeExpressionWithMini()
}
private fun parseFunctionTypeWithMini(): Pair<TypeDecl, MiniTypeRef>? {
val saved = cc.savePos()
val startPos = cc.currentPos()
fun parseParamTypes(): List<Pair<TypeDecl, MiniTypeRef>> {
val params = mutableListOf<Pair<TypeDecl, MiniTypeRef>>()
cc.skipWsTokens()
if (cc.peekNextNonWhitespace().type == Token.Type.RPAREN) {
cc.nextNonWhitespace()
return params
}
while (true) {
val (paramDecl, paramMini) = parseTypeExpressionWithMini()
params += paramDecl to paramMini
val sep = cc.nextNonWhitespace()
when (sep.type) {
Token.Type.COMMA -> continue
Token.Type.RPAREN -> return params
else -> sep.raiseSyntax("expected ',' or ')' in function type")
}
}
}
var receiverDecl: TypeDecl? = null
var receiverMini: MiniTypeRef? = null
val first = cc.peekNextNonWhitespace()
if (first.type == Token.Type.LPAREN) {
cc.nextNonWhitespace()
} else {
val recv = parseSimpleTypeExpressionWithMini()
val dotPos = cc.savePos()
if (cc.skipTokenOfType(Token.Type.DOT, isOptional = true) && cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
receiverDecl = recv.first
receiverMini = recv.second
cc.nextNonWhitespace()
} else {
cc.restorePos(saved)
return null
}
}
val params = parseParamTypes()
val arrow = cc.nextNonWhitespace()
if (arrow.type != Token.Type.ARROW) {
cc.restorePos(saved)
return null
}
val (retDecl, retMini) = parseTypeExpressionWithMini()
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
true
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
cc.pushPendingAssign()
true
} else false
val rangeStart = when (receiverMini) {
null -> startPos
else -> receiverMini.range.start
}
val rangeEnd = cc.currentPos()
val mini = MiniFunctionType(
range = MiniRange(rangeStart, rangeEnd),
receiver = receiverMini,
params = params.map { it.second },
returnType = retMini,
nullable = isNullable
)
val sem = TypeDecl.Function(
receiver = receiverDecl,
params = params.map { it.first },
returnType = retDecl,
nullable = isNullable
)
return sem to mini
}
private fun parseSimpleTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
// Parse a qualified base name: ID ('.' ID)*
val segments = mutableListOf<MiniTypeName.Segment>()
var first = true
@ -2295,7 +2460,11 @@ class Compiler(
val dotPos = cc.savePos()
val t = cc.next()
if (t.type == Token.Type.DOT) {
// continue
val nextAfterDot = cc.peekNextNonWhitespace()
if (nextAfterDot.type == Token.Type.LPAREN) {
cc.restorePos(dotPos)
break
}
continue
} else {
cc.restorePos(dotPos)
@ -2304,6 +2473,18 @@ class Compiler(
}
val qualified = segments.joinToString(".") { it.name }
val typeParams = currentTypeParams()
if (segments.size == 1 && typeParams.contains(qualified)) {
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
true
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
cc.pushPendingAssign()
true
} else false
val rangeEnd = cc.currentPos()
val miniRef = MiniTypeVar(MiniRange(typeStart, rangeEnd), qualified, isNullable)
return TypeDecl.TypeVar(qualified, isNullable) to miniRef
}
if (segments.size > 1) {
lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) }
} else {
@ -2366,6 +2547,44 @@ class Compiler(
return Pair(sem, miniRef)
}
private fun typeDeclToTypeRef(typeDecl: TypeDecl, pos: Pos): ObjRef {
return when (typeDecl) {
TypeDecl.TypeAny,
TypeDecl.TypeNullableAny,
is TypeDecl.TypeVar -> ConstRef(Obj.rootObjectType.asReadonly)
else -> {
val cls = resolveTypeDeclObjClass(typeDecl)
if (cls != null) return ConstRef(cls.asReadonly)
val name = typeDeclName(typeDecl)
resolveLocalTypeRef(name, pos)?.let { return it }
throw ScriptError(pos, "unknown type $name")
}
}
}
private fun typeDeclName(typeDecl: TypeDecl): String = when (typeDecl) {
is TypeDecl.Simple -> typeDecl.name
is TypeDecl.Generic -> typeDecl.name
is TypeDecl.Function -> "Callable"
is TypeDecl.TypeVar -> typeDecl.name
TypeDecl.TypeAny -> "Object"
TypeDecl.TypeNullableAny -> "Object?"
}
private fun resolveLocalTypeRef(name: String, pos: Pos): ObjRef? {
val slotLoc = lookupSlotLocation(name, includeModule = true) ?: return null
captureLocalRef(name, slotLoc, pos)?.let { return it }
return LocalSlotRef(
name,
slotLoc.slot,
slotLoc.scopeId,
slotLoc.isMutable,
slotLoc.isDelegated,
pos,
strictSlotRefs
)
}
/**
* Parse arguments list during the call and detect last block argument
* _following the parenthesis_ call: `(1,2) { ... }`
@ -2502,6 +2721,11 @@ class Compiler(
): ObjRef {
var detectedBlockArgument = blockArgument
val expectedReceiver = tailBlockReceiverType(left)
val withReceiver = when (left) {
is LocalVarRef -> if (left.name == "with") left.name else null
is LocalSlotRef -> if (left.name == "with") left.name else null
else -> null
}
val args = if (blockArgument) {
// Leading '{' has already been consumed by the caller token branch.
// Parse only the lambda expression as the last argument and DO NOT
@ -2511,9 +2735,24 @@ class Compiler(
val callableAccessor = parseLambdaExpression(expectedReceiver)
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
} else {
val r = parseArgs(expectedReceiver)
detectedBlockArgument = r.second
r.first
if (withReceiver != null) {
val parsedArgs = parseArgsNoTailBlock().toMutableList()
val pos = cc.savePos()
val end = cc.next()
if (end.type == Token.Type.LBRACE) {
val receiverType = inferReceiverTypeFromArgs(parsedArgs)
val callableAccessor = parseLambdaExpression(receiverType)
parsedArgs += ParsedArgument(ExpressionStatement(callableAccessor, end.pos), end.pos)
detectedBlockArgument = true
} else {
cc.restorePos(pos)
}
parsedArgs
} else {
val r = parseArgs(expectedReceiver)
detectedBlockArgument = r.second
r.first
}
}
val implicitThisTypeName = currentImplicitThisTypeName()
return when (left) {
@ -2571,6 +2810,26 @@ class Compiler(
}
}
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null
val ref = stmt.ref
val bySlot = (ref as? LocalSlotRef)?.let { slotRef ->
slotTypeByScopeId[slotRef.scopeId]?.get(slotRef.slot)
}
val byName = (ref as? LocalVarRef)?.let { nameObjClass[it.name] }
val cls = bySlot ?: byName ?: resolveInitializerObjClass(stmt)
return cls?.className
}
private fun inferReceiverTypeFromRef(ref: ObjRef): String? {
return when (ref) {
is LocalSlotRef -> slotTypeByScopeId[ref.scopeId]?.get(ref.slot)?.className
is LocalVarRef -> nameObjClass[ref.name]?.className
is QualifiedThisRef -> ref.typeName
else -> null
}
}
private suspend fun parseAccessor(): ObjRef? {
// could be: literal
val t = cc.next()
@ -3597,6 +3856,40 @@ class Compiler(
miniSink?.onClassDecl(node)
}
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
classCtx?.let { ctx ->
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
ctx.memberFieldIds.putAll(baseIds.fieldIds)
ctx.memberMethodIds.putAll(baseIds.methodIds)
ctx.nextFieldId = maxOf(ctx.nextFieldId, baseIds.nextFieldId)
ctx.nextMethodId = maxOf(ctx.nextMethodId, baseIds.nextMethodId)
for (member in ctx.declaredMembers) {
val isOverride = ctx.memberOverrides[member] == true
val hasBaseField = member in baseIds.fieldIds
val hasBaseMethod = member in baseIds.methodIds
if (isOverride) {
if (!hasBaseField && !hasBaseMethod) {
throw ScriptError(nameToken.pos, "member $member is marked 'override' but does not override anything")
}
} else {
if (hasBaseField || hasBaseMethod) {
throw ScriptError(nameToken.pos, "member $member overrides parent member but 'override' keyword is missing")
}
}
if (!ctx.memberFieldIds.containsKey(member)) {
ctx.memberFieldIds[member] = ctx.nextFieldId++
}
if (!ctx.memberMethodIds.containsKey(member)) {
ctx.memberMethodIds[member] = ctx.nextMethodId++
}
}
compileClassInfos[nameToken.value] = CompileClassInfo(
name = nameToken.value,
fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
nextMethodId = ctx.nextMethodId
)
}
// restore if no body starts here
cc.restorePos(saved)
null
@ -4067,6 +4360,25 @@ class Compiler(
declareLocalName(extensionWrapperName, isMutable = false)
}
val typeParams = mutableSetOf<String>()
if (cc.peekNextNonWhitespace().type == Token.Type.LT) {
cc.nextNonWhitespace()
while (true) {
val idTok = cc.requireToken(Token.Type.ID, "type parameter name expected")
typeParams.add(idTok.value)
val sep = cc.nextNonWhitespace()
when (sep.type) {
Token.Type.COMMA -> continue
Token.Type.GT -> break
Token.Type.SHR -> {
cc.pushPendingGT()
break
}
else -> sep.raiseSyntax("expected ',' or '>' in type parameter list")
}
}
}
val argsDeclaration: ArgsDeclaration =
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
cc.nextNonWhitespace() // consume (
@ -4128,7 +4440,8 @@ class Compiler(
CodeContext.Function(
name,
implicitThisMembers = implicitThisMembers,
implicitThisTypeName = extTypeName
implicitThisTypeName = extTypeName,
typeParams = typeParams
)
) {
cc.labels.add(name)
@ -4545,6 +4858,8 @@ class Compiler(
val rawName = when (type) {
is TypeDecl.Simple -> type.name
is TypeDecl.Generic -> type.name
is TypeDecl.Function -> "Callable"
is TypeDecl.TypeVar -> return null
else -> return null
}
val name = rawName.substringAfterLast('.')
@ -4986,6 +5301,12 @@ class Compiler(
val slotIndex = slotPlan?.slots?.get(name)?.index
val scopeId = slotPlan?.id
val initObjClass = resolveInitializerObjClass(initialExpression) ?: resolveTypeDeclObjClass(varTypeDecl)
if (initObjClass != null) {
if (slotIndex != null && scopeId != null) {
slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass
}
nameObjClass[name] = initObjClass
}
return VarDeclStatement(
name,
isMutable,

View File

@ -23,7 +23,13 @@ package net.sergeych.lyng
// very soon
sealed class TypeDecl(val isNullable:Boolean = false) {
// ??
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
data class Function(
val receiver: TypeDecl?,
val params: List<TypeDecl>,
val returnType: TypeDecl,
val nullable: Boolean = false
) : TypeDecl(nullable)
data class TypeVar(val name: String, val nullable: Boolean = false) : TypeDecl(nullable)
object TypeAny : TypeDecl()
object TypeNullableAny : TypeDecl(true)

View File

@ -4368,6 +4368,7 @@ class BytecodeCompiler(
is ListLiteralRef -> ObjList.type
is MapLiteralRef -> ObjMap.type
is RangeRef -> ObjRange.type
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverClass(it.ref) }
is ConstRef -> when (ref.constValue) {
is ObjList -> ObjList.type
is ObjMap -> ObjMap.type
@ -4397,7 +4398,13 @@ class BytecodeCompiler(
} else {
when (ref.name) {
"map",
"mapNotNull",
"filter",
"filterNotNull",
"drop",
"take",
"flatMap",
"flatten",
"sorted",
"sortedBy",
"sortedWith",
@ -4405,6 +4412,9 @@ class BytecodeCompiler(
"toList",
"shuffle",
"shuffled" -> ObjList.type
"dropLast" -> ObjFlow.type
"takeLast" -> ObjRingBuffer.type
"count" -> ObjInt.type
"toSet" -> ObjSet.type
"toMap" -> ObjMap.type
"joinToString" -> ObjString.type
@ -4429,6 +4439,7 @@ class BytecodeCompiler(
is ListLiteralRef -> ObjList.type
is MapLiteralRef -> ObjMap.type
is RangeRef -> ObjRange.type
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverClassForScopeCollection(it.ref) }
is ConstRef -> when (ref.constValue) {
is ObjList -> ObjList.type
is ObjMap -> ObjMap.type
@ -4449,8 +4460,34 @@ class BytecodeCompiler(
}
is MethodCallRef -> {
val targetClass = resolveReceiverClassForScopeCollection(ref.receiver) ?: return null
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) ObjRegex.type
else null
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
ObjRegex.type
} else {
when (ref.name) {
"map",
"mapNotNull",
"filter",
"filterNotNull",
"drop",
"take",
"flatMap",
"flatten",
"sorted",
"sortedBy",
"sortedWith",
"reversed",
"toList",
"shuffle",
"shuffled" -> ObjList.type
"dropLast" -> ObjFlow.type
"takeLast" -> ObjRingBuffer.type
"count" -> ObjInt.type
"toSet" -> ObjSet.type
"toMap" -> ObjMap.type
"joinToString" -> ObjString.type
else -> null
}
}
}
else -> null
}

View File

@ -36,20 +36,19 @@ val ObjIterable by lazy {
code = null
)
addPropertyDoc(
addFnDoc(
name = "toList",
doc = "Collect elements of this iterable into a new list.",
type = type("lyng.List"),
moduleName = "lyng.stdlib",
getter = {
val result = mutableListOf<Obj>()
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(this, "next"))
}
ObjList(result)
returns = type("lyng.List"),
moduleName = "lyng.stdlib"
) {
val result = mutableListOf<Obj>()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(this, "next"))
}
)
ObjList(result)
}
// it is not effective, but it is open:
addFnDoc(

View File

@ -247,6 +247,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
companion object {
val type = object : ObjClass("List", ObjArray) {
override suspend fun callOn(scope: Scope): Obj {
return ObjList(scope.args.list.toMutableList())
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
return ObjList(decoder.decodeAnyList(scope))
}
@ -519,4 +523,3 @@ fun <T>MutableList<T>.swap(i: Int, j: Int) {
}
}

View File

@ -17,10 +17,8 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore
class StdlibTest {
@Test
fun testIterableFilter() = runTest {

View File

@ -229,7 +229,7 @@ fun Iterable.sortedBy(predicate) {
/* Return a shuffled copy of the iterable as a list. */
fun Iterable.shuffled() {
val list: List = toList
val list: List = toList()
list.shuffle()
list
}
@ -327,10 +327,10 @@ interface Delegate {
Executes the block with `this` set to self and
returns what the block returns.
*/
fun with(self, block) {
fun with<T,R>(self: T, block: T.()->R): R {
var result = Unset
self.apply { result = block() }
result
result as R
}
/*

View File

@ -4,7 +4,7 @@ better than even in C++ ;)
```lyng
fun t(x) {
// x is Object, and x is nullable
// x is Object (non-null)
println(x)
}
@ -117,6 +117,10 @@ Notes and open questions to answer in this spec:
Not null by default (Object), must be specified with `?` suffix. We use Kotlin-style `!!` for non-null assertion. Therefore we check nullability at compile time, and we throw NPE only at `x!!` or `obj as X` (if obj is nullable, it is same as `obj!! as X`).
Return type inference and nullability:
- If any branch or return expression is nullable, the inferred return type is nullable.
- This is independent of whether `return` is used or implicit last-expression rules apply.
- Default type of untyped values: If a parameter has no type and no default, is it Object? (dynamic), or a new top type?
Lets discuss in more details. For example:
@ -205,6 +209,11 @@ I think we can omit if not used. For kotlin interop: if the class has at least o
Compile time error unless it is an Object own method. Let's force rewriting existing code in favor of explicit casts. It will repay itself: I laready have a project on Lyng that suffers from implicit casts har to trace errors.
No runtime lookups or fallbacks:
- All symbol and member resolution must be done at compile time.
- If an extension is not known at compile time (not imported or declared before use), it is a compile-time error.
- Runtime lookup is only possible via explicit reflection helpers.
Example:
```lyng
fun f(x) { // x: Object