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( class Function(
val name: String, val name: String,
val implicitThisMembers: Boolean = false, val implicitThisMembers: Boolean = false,
val implicitThisTypeName: String? = null val implicitThisTypeName: String? = null,
val typeParams: Set<String> = emptySet()
): CodeContext() ): CodeContext()
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() { class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
val pendingInitializations = mutableMapOf<String, Pos>() val pendingInitializations = mutableMapOf<String, Pos>()

View File

@ -103,6 +103,8 @@ class Compiler(
} }
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull() 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) { private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
@ -112,6 +114,36 @@ class Compiler(
if (!record.visibility.isPublic) continue if (!record.visibility.isPublic) continue
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated) 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()) { for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
val record = current.getSlotRecord(slotIndex) val record = current.getSlotRecord(slotIndex)
if (!record.visibility.isPublic) continue if (!record.visibility.isPublic) continue
@ -126,6 +158,8 @@ class Compiler(
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
val saved = cc.savePos() val saved = cc.savePos()
var depth = 0 var depth = 0
var parenDepth = 0
var bracketDepth = 0
fun nextNonWs(): Token { fun nextNonWs(): Token {
var t = cc.next() var t = cc.next()
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) { 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) { when (t.type) {
Token.Type.LBRACE -> depth++ Token.Type.LBRACE -> depth++
Token.Type.RBRACE -> if (depth > 0) 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) { Token.Type.ID -> if (depth == 0) {
if (parenDepth > 0 || bracketDepth > 0) continue
when (t.value) { when (t.value) {
"fun", "fn" -> { "fun", "fn" -> {
val nameToken = nextNonWs() val nameToken = nextNonWs()
@ -399,6 +438,14 @@ class Compiler(
return null 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? { private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? {
for (i in slotPlanStack.indices.reversed()) { for (i in slotPlanStack.indices.reversed()) {
if (!includeModule && i == 0) continue if (!includeModule && i == 0) continue
@ -468,6 +515,15 @@ class Compiler(
val ids = resolveMemberIds(name, pos, null) val ids = resolveMemberIds(name, pos, null)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName()) 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 modulePlan = moduleSlotPlan()
val moduleEntry = modulePlan?.slots?.get(name) val moduleEntry = modulePlan?.slots?.get(name)
if (moduleEntry != null) { if (moduleEntry != null) {
@ -514,15 +570,6 @@ class Compiler(
return ref 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 } val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
if (classContext && extensionNames.contains(name)) { if (classContext && extensionNames.contains(name)) {
resolutionSink?.referenceMember(name, pos) resolutionSink?.referenceMember(name, pos)
@ -915,9 +962,16 @@ class Compiler(
private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds { private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds {
if (implicitTypeName == null) return resolveMemberIds(name, pos, null) if (implicitTypeName == null) return resolveMemberIds(name, pos, null)
val info = resolveCompileClassInfo(implicitTypeName) val info = resolveCompileClassInfo(implicitTypeName)
?: if (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $implicitTypeName") val fieldId = info?.fieldIds?.get(name)
val fieldId = info.fieldIds[name] val methodId = info?.methodIds?.get(name)
val methodId = info.methodIds[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 (fieldId == null && methodId == null) {
if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null) if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null)
if (allowUnresolvedRefs) return MemberIds(null, null) if (allowUnresolvedRefs) return MemberIds(null, null)
@ -925,6 +979,19 @@ class Compiler(
} }
return MemberIds(fieldId, methodId) 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> private val currentRangeParamNames: Set<String>
get() = rangeParamNamesStack.lastOrNull() ?: emptySet() get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
private val capturePlanStack = mutableListOf<CapturePlan>() private val capturePlanStack = mutableListOf<CapturePlan>()
@ -1443,10 +1510,19 @@ class Compiler(
break break
} }
val rvalue = parseExpressionLevel(level + 1) val res = if (opToken.type == Token.Type.AS || opToken.type == Token.Type.ASNULL) {
?: throw ScriptError(opToken.pos, "Expecting expression") val (typeDecl, _) = parseTypeExpressionWithMini()
val typeRef = typeDeclToTypeRef(typeDecl, opToken.pos)
val res = op.generate(opToken.pos, lvalue!!, rvalue) 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) { if (opToken.type == Token.Type.ASSIGN) {
val ctx = codeContexts.lastOrNull() val ctx = codeContexts.lastOrNull()
if (ctx is CodeContext.ClassBody) { if (ctx is CodeContext.ClassBody) {
@ -1527,7 +1603,10 @@ class Compiler(
Token.Type.LPAREN -> { Token.Type.LPAREN -> {
cc.next() cc.next()
// instance method call // 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 args = parsed.first
val tailBlock = parsed.second val tailBlock = parsed.second
if (left is LocalVarRef && left.name == "scope") { if (left is LocalVarRef && left.name == "scope") {
@ -1578,7 +1657,10 @@ class Compiler(
// single lambda arg, like assertThrows { ... } // single lambda arg, like assertThrows { ... }
cc.next() cc.next()
isCall = true 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 argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos)) val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
operand = when (left) { operand = when (left) {
@ -2274,6 +2356,89 @@ class Compiler(
} }
private fun parseTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> { 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)* // Parse a qualified base name: ID ('.' ID)*
val segments = mutableListOf<MiniTypeName.Segment>() val segments = mutableListOf<MiniTypeName.Segment>()
var first = true var first = true
@ -2295,7 +2460,11 @@ class Compiler(
val dotPos = cc.savePos() val dotPos = cc.savePos()
val t = cc.next() val t = cc.next()
if (t.type == Token.Type.DOT) { if (t.type == Token.Type.DOT) {
// continue val nextAfterDot = cc.peekNextNonWhitespace()
if (nextAfterDot.type == Token.Type.LPAREN) {
cc.restorePos(dotPos)
break
}
continue continue
} else { } else {
cc.restorePos(dotPos) cc.restorePos(dotPos)
@ -2304,6 +2473,18 @@ class Compiler(
} }
val qualified = segments.joinToString(".") { it.name } 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) { if (segments.size > 1) {
lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) } lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) }
} else { } else {
@ -2366,6 +2547,44 @@ class Compiler(
return Pair(sem, miniRef) 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 * Parse arguments list during the call and detect last block argument
* _following the parenthesis_ call: `(1,2) { ... }` * _following the parenthesis_ call: `(1,2) { ... }`
@ -2502,6 +2721,11 @@ class Compiler(
): ObjRef { ): ObjRef {
var detectedBlockArgument = blockArgument var detectedBlockArgument = blockArgument
val expectedReceiver = tailBlockReceiverType(left) 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) { val args = if (blockArgument) {
// Leading '{' has already been consumed by the caller token branch. // Leading '{' has already been consumed by the caller token branch.
// Parse only the lambda expression as the last argument and DO NOT // Parse only the lambda expression as the last argument and DO NOT
@ -2511,9 +2735,24 @@ class Compiler(
val callableAccessor = parseLambdaExpression(expectedReceiver) val callableAccessor = parseLambdaExpression(expectedReceiver)
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos())) listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
} else { } else {
val r = parseArgs(expectedReceiver) if (withReceiver != null) {
detectedBlockArgument = r.second val parsedArgs = parseArgsNoTailBlock().toMutableList()
r.first 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() val implicitThisTypeName = currentImplicitThisTypeName()
return when (left) { 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? { private suspend fun parseAccessor(): ObjRef? {
// could be: literal // could be: literal
val t = cc.next() val t = cc.next()
@ -3597,6 +3856,40 @@ class Compiler(
miniSink?.onClassDecl(node) miniSink?.onClassDecl(node)
} }
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos) 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 // restore if no body starts here
cc.restorePos(saved) cc.restorePos(saved)
null null
@ -4067,6 +4360,25 @@ class Compiler(
declareLocalName(extensionWrapperName, isMutable = false) 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 = val argsDeclaration: ArgsDeclaration =
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) { if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
cc.nextNonWhitespace() // consume ( cc.nextNonWhitespace() // consume (
@ -4128,7 +4440,8 @@ class Compiler(
CodeContext.Function( CodeContext.Function(
name, name,
implicitThisMembers = implicitThisMembers, implicitThisMembers = implicitThisMembers,
implicitThisTypeName = extTypeName implicitThisTypeName = extTypeName,
typeParams = typeParams
) )
) { ) {
cc.labels.add(name) cc.labels.add(name)
@ -4545,6 +4858,8 @@ class Compiler(
val rawName = when (type) { val rawName = when (type) {
is TypeDecl.Simple -> type.name is TypeDecl.Simple -> type.name
is TypeDecl.Generic -> type.name is TypeDecl.Generic -> type.name
is TypeDecl.Function -> "Callable"
is TypeDecl.TypeVar -> return null
else -> return null else -> return null
} }
val name = rawName.substringAfterLast('.') val name = rawName.substringAfterLast('.')
@ -4986,6 +5301,12 @@ class Compiler(
val slotIndex = slotPlan?.slots?.get(name)?.index val slotIndex = slotPlan?.slots?.get(name)?.index
val scopeId = slotPlan?.id val scopeId = slotPlan?.id
val initObjClass = resolveInitializerObjClass(initialExpression) ?: resolveTypeDeclObjClass(varTypeDecl) 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( return VarDeclStatement(
name, name,
isMutable, isMutable,

View File

@ -23,7 +23,13 @@ package net.sergeych.lyng
// very soon // very soon
sealed class TypeDecl(val isNullable:Boolean = false) { 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 TypeAny : TypeDecl()
object TypeNullableAny : TypeDecl(true) object TypeNullableAny : TypeDecl(true)

View File

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

View File

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

View File

@ -247,6 +247,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
companion object { companion object {
val type = object : ObjClass("List", ObjArray) { 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 { override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
return ObjList(decoder.decodeAnyList(scope)) 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 kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
@Ignore
class StdlibTest { class StdlibTest {
@Test @Test
fun testIterableFilter() = runTest { fun testIterableFilter() = runTest {

View File

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

View File

@ -4,7 +4,7 @@ better than even in C++ ;)
```lyng ```lyng
fun t(x) { fun t(x) {
// x is Object, and x is nullable // x is Object (non-null)
println(x) 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`). 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? - 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: 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. 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: Example:
```lyng ```lyng
fun f(x) { // x: Object fun f(x) { // x: Object