Compare commits

..

No commits in common. "54c6fca0e849bab5f8fe2356d984c284b137a8be" and "51b397686dc92e21f52928fb2812ff85383956b9" have entirely different histories.

12 changed files with 124 additions and 772 deletions

View File

@ -22,13 +22,9 @@ sealed class CodeContext {
class Function(
val name: String,
val implicitThisMembers: Boolean = false,
val implicitThisTypeName: String? = null,
val typeParams: Set<String> = emptySet(),
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
val implicitThisTypeName: String? = null
): CodeContext()
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
var typeParams: Set<String> = emptySet()
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
val pendingInitializations = mutableMapOf<String, Pos>()
val declaredMembers = mutableSetOf<String>()
val memberOverrides = mutableMapOf<String, Boolean>()

View File

@ -64,38 +64,12 @@ class Compiler(
)
private val slotPlanStack = mutableListOf<SlotPlan>()
private var nextScopeId = 0
private val genericFunctionDeclsStack = mutableListOf<MutableMap<String, GenericFunctionDecl>>(mutableMapOf())
// Track declared local variables count per function for precise capacity hints
private val localDeclCountStack = mutableListOf<Int>()
private val currentLocalDeclCount: Int
get() = localDeclCountStack.lastOrNull() ?: 0
private data class GenericFunctionDecl(
val typeParams: List<TypeDecl.TypeParam>,
val params: List<ArgsDeclaration.Item>,
val pos: Pos
)
private fun pushGenericFunctionScope() {
genericFunctionDeclsStack.add(mutableMapOf())
}
private fun popGenericFunctionScope() {
genericFunctionDeclsStack.removeLast()
}
private fun currentGenericFunctionDecls(): MutableMap<String, GenericFunctionDecl> {
return genericFunctionDeclsStack.last()
}
private fun lookupGenericFunctionDecl(name: String): GenericFunctionDecl? {
for (i in genericFunctionDeclsStack.indices.reversed()) {
genericFunctionDeclsStack[i][name]?.let { return it }
}
return null
}
private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T {
localNamesStack.add(names.toMutableSet())
return try {
@ -129,8 +103,6 @@ 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
@ -140,36 +112,6 @@ 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
@ -184,8 +126,6 @@ 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) {
@ -199,12 +139,7 @@ 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()
@ -464,62 +399,6 @@ class Compiler(
return null
}
private fun currentTypeParams(): Set<String> {
val result = mutableSetOf<String>()
pendingTypeParamStack.lastOrNull()?.let { result.addAll(it) }
for (ctx in codeContexts.asReversed()) {
when (ctx) {
is CodeContext.Function -> result.addAll(ctx.typeParams)
is CodeContext.ClassBody -> result.addAll(ctx.typeParams)
else -> {}
}
}
return result
}
private val pendingTypeParamStack = mutableListOf<Set<String>>()
private fun parseTypeParamList(): List<TypeDecl.TypeParam> {
if (cc.peekNextNonWhitespace().type != Token.Type.LT) return emptyList()
val typeParams = mutableListOf<TypeDecl.TypeParam>()
cc.nextNonWhitespace()
while (true) {
val varianceToken = cc.peekNextNonWhitespace()
val variance = when (varianceToken.value) {
"in" -> {
cc.nextNonWhitespace()
TypeDecl.Variance.In
}
"out" -> {
cc.nextNonWhitespace()
TypeDecl.Variance.Out
}
else -> TypeDecl.Variance.Invariant
}
val idTok = cc.requireToken(Token.Type.ID, "type parameter name expected")
var bound: TypeDecl? = null
var defaultType: TypeDecl? = null
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
bound = parseTypeExpressionWithMini().first
}
if (cc.skipTokenOfType(Token.Type.ASSIGN, isOptional = true)) {
defaultType = parseTypeExpressionWithMini().first
}
typeParams.add(TypeDecl.TypeParam(idTok.value, variance, bound, defaultType))
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")
}
}
return typeParams
}
private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? {
for (i in slotPlanStack.indices.reversed()) {
if (!includeModule && i == 0) continue
@ -589,15 +468,6 @@ 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) {
@ -644,6 +514,15 @@ 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)
@ -803,7 +682,6 @@ class Compiler(
private suspend fun <T> inCodeContext(context: CodeContext, f: suspend () -> T): T {
codeContexts.add(context)
pushGenericFunctionScope()
try {
val res = f()
if (context is CodeContext.ClassBody) {
@ -814,7 +692,6 @@ class Compiler(
}
return res
} finally {
popGenericFunctionScope()
codeContexts.removeLast()
}
}
@ -1038,16 +915,9 @@ class Compiler(
private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds {
if (implicitTypeName == null) return resolveMemberIds(name, pos, null)
val info = resolveCompileClassInfo(implicitTypeName)
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 (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $implicitTypeName")
val fieldId = info.fieldIds[name]
val methodId = info.methodIds[name]
if (fieldId == null && methodId == null) {
if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null)
if (allowUnresolvedRefs) return MemberIds(null, null)
@ -1055,19 +925,6 @@ 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>()
@ -1586,19 +1443,10 @@ class Compiler(
break
}
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)
}
val rvalue = parseExpressionLevel(level + 1)
?: throw ScriptError(opToken.pos, "Expecting expression")
val res = op.generate(opToken.pos, lvalue!!, rvalue)
if (opToken.type == Token.Type.ASSIGN) {
val ctx = codeContexts.lastOrNull()
if (ctx is CodeContext.ClassBody) {
@ -1679,10 +1527,7 @@ class Compiler(
Token.Type.LPAREN -> {
cc.next()
// instance method call
val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val parsed = parseArgs(receiverType)
val parsed = parseArgs()
val args = parsed.first
val tailBlock = parsed.second
if (left is LocalVarRef && left.name == "scope") {
@ -1733,10 +1578,7 @@ class Compiler(
// single lambda arg, like assertThrows { ... }
cc.next()
isCall = true
val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val lambda = parseLambdaExpression(receiverType)
val lambda = parseLambdaExpression()
val argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
operand = when (left) {
@ -2432,119 +2274,6 @@ class Compiler(
}
private fun parseTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
return parseTypeUnionWithMini()
}
private fun parseTypeUnionWithMini(): Pair<TypeDecl, MiniTypeRef> {
var left = parseTypeIntersectionWithMini()
val options = mutableListOf(left)
while (cc.skipTokenOfType(Token.Type.BITOR, isOptional = true)) {
options += parseTypeIntersectionWithMini()
}
if (options.size == 1) return left
val rangeStart = options.first().second.range.start
val rangeEnd = cc.currentPos()
val mini = MiniTypeUnion(MiniRange(rangeStart, rangeEnd), options.map { it.second }, nullable = false)
return TypeDecl.Union(options.map { it.first }, nullable = false) to mini
}
private fun parseTypeIntersectionWithMini(): Pair<TypeDecl, MiniTypeRef> {
var left = parseTypePrimaryWithMini()
val options = mutableListOf(left)
while (cc.skipTokenOfType(Token.Type.BITAND, isOptional = true)) {
options += parseTypePrimaryWithMini()
}
if (options.size == 1) return left
val rangeStart = options.first().second.range.start
val rangeEnd = cc.currentPos()
val mini = MiniTypeIntersection(MiniRange(rangeStart, rangeEnd), options.map { it.second }, nullable = false)
return TypeDecl.Intersection(options.map { it.first }, nullable = false) to mini
}
private fun parseTypePrimaryWithMini(): 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
@ -2566,11 +2295,7 @@ class Compiler(
val dotPos = cc.savePos()
val t = cc.next()
if (t.type == Token.Type.DOT) {
val nextAfterDot = cc.peekNextNonWhitespace()
if (nextAfterDot.type == Token.Type.LPAREN) {
cc.restorePos(dotPos)
break
}
// continue
continue
} else {
cc.restorePos(dotPos)
@ -2579,18 +2304,6 @@ 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 {
@ -2653,126 +2366,6 @@ class Compiler(
return Pair(sem, miniRef)
}
private fun typeDeclToTypeRef(typeDecl: TypeDecl, pos: Pos): ObjRef {
return when (typeDecl) {
TypeDecl.TypeAny,
TypeDecl.TypeNullableAny -> ConstRef(Obj.rootObjectType.asReadonly)
is TypeDecl.TypeVar -> resolveLocalTypeRef(typeDecl.name, pos) ?: 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
is TypeDecl.Union -> typeDecl.options.joinToString(" | ") { typeDeclName(it) }
is TypeDecl.Intersection -> typeDecl.options.joinToString(" & ") { typeDeclName(it) }
TypeDecl.TypeAny -> "Object"
TypeDecl.TypeNullableAny -> "Object?"
}
private fun inferObjClassFromRef(ref: ObjRef): ObjClass? = when (ref) {
is ConstRef -> ref.constValue as? ObjClass ?: (ref.constValue as? Obj)?.objClass
is LocalVarRef -> nameObjClass[ref.name]
is LocalSlotRef -> nameObjClass[ref.name]
is ListLiteralRef -> ObjList.type
is MapLiteralRef -> ObjMap.type
is RangeRef -> ObjRange.type
is CastRef -> resolveTypeRefClass(ref.castTypeRef())
else -> null
}
private fun resolveTypeRefClass(ref: ObjRef): ObjClass? = when (ref) {
is ConstRef -> ref.constValue as? ObjClass
is LocalSlotRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
is LocalVarRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
else -> null
}
private fun typeParamBoundSatisfied(argClass: ObjClass, bound: TypeDecl): Boolean = when (bound) {
is TypeDecl.Union -> bound.options.any { typeParamBoundSatisfied(argClass, it) }
is TypeDecl.Intersection -> bound.options.all { typeParamBoundSatisfied(argClass, it) }
is TypeDecl.Simple, is TypeDecl.Generic -> {
val boundClass = resolveTypeDeclObjClass(bound) ?: return false
argClass == boundClass || argClass.allParentsSet.contains(boundClass)
}
else -> true
}
private fun checkGenericBoundsAtCall(
name: String,
args: List<ParsedArgument>,
pos: Pos
) {
val decl = lookupGenericFunctionDecl(name) ?: return
val inferred = mutableMapOf<String, ObjClass>()
val limit = minOf(args.size, decl.params.size)
for (i in 0 until limit) {
val paramType = decl.params[i].type
val argRef = (args[i].value as? ExpressionStatement)?.ref ?: continue
val argClass = inferObjClassFromRef(argRef) ?: continue
if (paramType is TypeDecl.TypeVar) {
inferred[paramType.name] = argClass
}
}
for (tp in decl.typeParams) {
val argClass = inferred[tp.name] ?: continue
val bound = tp.bound ?: continue
if (!typeParamBoundSatisfied(argClass, bound)) {
throw ScriptError(pos, "type argument ${argClass.className} does not satisfy bound ${typeDeclName(bound)}")
}
}
}
private fun bindTypeParamsAtRuntime(
context: Scope,
argsDeclaration: ArgsDeclaration,
typeParams: List<TypeDecl.TypeParam>
) {
if (typeParams.isEmpty()) return
val inferred = mutableMapOf<String, ObjClass>()
for (param in argsDeclaration.params) {
val paramType = param.type
if (paramType is TypeDecl.TypeVar) {
val rec = context.getLocalRecordDirect(param.name) ?: continue
val value = rec.value
if (value is Obj) inferred[paramType.name] = value.objClass
}
}
for (tp in typeParams) {
val cls = inferred[tp.name]
?: tp.defaultType?.let { resolveTypeDeclObjClass(it) }
?: Obj.rootObjectType
context.addConst(tp.name, cls)
val bound = tp.bound ?: continue
if (!typeParamBoundSatisfied(cls, bound)) {
context.raiseError("type argument ${cls.className} does not satisfy bound ${typeDeclName(bound)}")
}
}
}
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) { ... }`
@ -2909,11 +2502,6 @@ 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
@ -2923,24 +2511,9 @@ class Compiler(
val callableAccessor = parseLambdaExpression(expectedReceiver)
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
} else {
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 r = parseArgs(expectedReceiver)
detectedBlockArgument = r.second
r.first
}
val implicitThisTypeName = currentImplicitThisTypeName()
return when (left) {
@ -2971,7 +2544,6 @@ class Compiler(
implicitThisTypeName
)
} else {
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional)
}
}
@ -2992,7 +2564,6 @@ class Compiler(
implicitThisTypeName
)
} else {
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional)
}
}
@ -3000,26 +2571,6 @@ 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()
@ -3892,20 +3443,10 @@ class Compiler(
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) {
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
val typeParamDecls = parseTypeParamList()
classCtx?.typeParamDecls = typeParamDecls
val classTypeParams = typeParamDecls.map { it.name }.toSet()
classCtx?.typeParams = classTypeParams
pendingTypeParamStack.add(classTypeParams)
val constructorArgsDeclaration: ArgsDeclaration?
try {
constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true)
else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
} finally {
pendingTypeParamStack.removeLast()
}
val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true)
else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError(
@ -3929,27 +3470,18 @@ class Compiler(
data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
val baseSpecs = mutableListOf<BaseSpec>()
pendingTypeParamStack.add(classTypeParams)
try {
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
do {
val (baseDecl, _) = parseSimpleTypeExpressionWithMini()
val baseName = when (baseDecl) {
is TypeDecl.Simple -> baseDecl.name
is TypeDecl.Generic -> baseDecl.name
else -> throw ScriptError(cc.currentPos(), "base class name expected")
}
var argsList: List<ParsedArgument>? = null
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
// Parse args without consuming any following block so that a class body can follow safely
argsList = parseArgsNoTailBlock()
}
baseSpecs += BaseSpec(baseName, argsList)
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
}
} finally {
pendingTypeParamStack.removeLast()
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
do {
val baseId = cc.requireToken(Token.Type.ID, "base class name expected")
resolutionSink?.reference(baseId.value, baseId.pos)
var argsList: List<ParsedArgument>? = null
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
// Parse args without consuming any following block so that a class body can follow safely
argsList = parseArgsNoTailBlock()
}
baseSpecs += BaseSpec(baseId.value, argsList)
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
}
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
@ -4065,40 +3597,6 @@ 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
@ -4569,29 +4067,16 @@ class Compiler(
declareLocalName(extensionWrapperName, isMutable = false)
}
val typeParamDecls = parseTypeParamList()
val typeParams = typeParamDecls.map { it.name }.toSet()
pendingTypeParamStack.add(typeParams)
val argsDeclaration: ArgsDeclaration
val returnTypeMini: MiniTypeRef?
try {
argsDeclaration =
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
cc.nextNonWhitespace() // consume (
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
} else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
val argsDeclaration: ArgsDeclaration =
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
cc.nextNonWhitespace() // consume (
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
} else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
if (typeParamDecls.isNotEmpty() && declKind != SymbolKind.MEMBER) {
currentGenericFunctionDecls()[name] = GenericFunctionDecl(typeParamDecls, argsDeclaration.params, nameStartPos)
}
// Optional return type
returnTypeMini = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
parseTypeDeclarationWithMini().second
} else null
} finally {
pendingTypeParamStack.removeLast()
}
// Optional return type
val returnTypeMini: MiniTypeRef? = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
parseTypeDeclarationWithMini().second
} else null
var isDelegated = false
var delegateExpression: Statement? = null
@ -4643,18 +4128,15 @@ class Compiler(
CodeContext.Function(
name,
implicitThisMembers = implicitThisMembers,
implicitThisTypeName = extTypeName,
typeParams = typeParams,
typeParamDecls = typeParamDecls
implicitThisTypeName = extTypeName
)
) {
cc.labels.add(name)
outerLabel?.let { cc.labels.add(it) }
val paramNamesList = argsDeclaration.params.map { it.name }
val typeParamNames = typeParamDecls.map { it.name }
val paramNames: Set<String> = paramNamesList.toSet()
val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames)
val paramSlotPlan = buildParamSlotPlan(paramNamesList)
val capturePlan = CapturePlan(paramSlotPlan)
val rangeParamNames = argsDeclaration.params
.filter { isRangeType(it.type) }
@ -4756,7 +4238,6 @@ class Compiler(
// load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
bindTypeParamsAtRuntime(context, argsDeclaration, typeParamDecls)
if (extTypeName != null) {
context.thisObj = callerContext.thisObj
}
@ -5064,10 +4545,6 @@ 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
is TypeDecl.Union -> return null
is TypeDecl.Intersection -> return null
else -> return null
}
val name = rawName.substringAfterLast('.')
@ -5509,12 +4986,6 @@ 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

@ -22,23 +22,8 @@ package net.sergeych.lyng
// this is highly experimental and subject to complete redesign
// very soon
sealed class TypeDecl(val isNullable:Boolean = false) {
enum class Variance { In, Out, Invariant }
// ??
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)
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)
data class TypeParam(
val name: String,
val variance: Variance = Variance.Invariant,
val bound: TypeDecl? = null,
val defaultType: TypeDecl? = null
)
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
object TypeAny : TypeDecl()
object TypeNullableAny : TypeDecl(true)

View File

@ -4368,7 +4368,6 @@ 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
@ -4398,13 +4397,7 @@ class BytecodeCompiler(
} else {
when (ref.name) {
"map",
"mapNotNull",
"filter",
"filterNotNull",
"drop",
"take",
"flatMap",
"flatten",
"sorted",
"sortedBy",
"sortedWith",
@ -4412,9 +4405,6 @@ 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
@ -4439,7 +4429,6 @@ 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
@ -4460,34 +4449,8 @@ 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 {
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
}
}
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) ObjRegex.type
else null
}
else -> null
}

View File

@ -1025,8 +1025,6 @@ object DocLookupUtils {
is MiniGenericType -> simpleClassNameOf(t.base)
is MiniFunctionType -> null
is MiniTypeVar -> null
is MiniTypeUnion -> null
is MiniTypeIntersection -> null
}
fun typeOf(t: MiniTypeRef?): String = when (t) {
@ -1037,8 +1035,6 @@ object DocLookupUtils {
r + "(" + t.params.joinToString(", ") { typeOf(it) } + ") -> " + typeOf(t.returnType) + (if (t.nullable) "?" else "")
}
is MiniTypeVar -> t.name + (if (t.nullable) "?" else "")
is MiniTypeUnion -> t.options.joinToString(" | ") { typeOf(it) } + (if (t.nullable) "?" else "")
is MiniTypeIntersection -> t.options.joinToString(" & ") { typeOf(it) } + (if (t.nullable) "?" else "")
null -> ""
}

View File

@ -150,18 +150,6 @@ data class MiniTypeVar(
val nullable: Boolean
) : MiniTypeRef
data class MiniTypeUnion(
override val range: MiniRange,
val options: List<MiniTypeRef>,
val nullable: Boolean
) : MiniTypeRef
data class MiniTypeIntersection(
override val range: MiniRange,
val options: List<MiniTypeRef>,
val nullable: Boolean
) : MiniTypeRef
// Script and declarations (lean subset; can be extended later)
sealed interface MiniNamedDecl : MiniNode {
val name: String

View File

@ -36,19 +36,20 @@ val ObjIterable by lazy {
code = null
)
addFnDoc(
addPropertyDoc(
name = "toList",
doc = "Collect elements of this iterable into a new list.",
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"))
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)
}
ObjList(result)
}
)
// it is not effective, but it is open:
addFnDoc(

View File

@ -247,10 +247,6 @@ 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))
}
@ -523,3 +519,4 @@ fun <T>MutableList<T>.swap(i: Int, j: Int) {
}
}

View File

@ -5273,40 +5273,6 @@ class ScriptTest {
assertEquals(ObjFalse, scope.eval("isInt(\"42\")"))
}
@Test
fun testGenericBoundsAndReifiedTypeParams() = runTest {
val resInt = eval(
"""
fun square<T: Int | Real>(x: T) = x * x
square(2)
""".trimIndent()
)
assertEquals(4L, (resInt as ObjInt).value)
val resReal = eval(
"""
fun square<T: Int | Real>(x: T) = x * x
square(1.5)
""".trimIndent()
)
assertEquals(2.25, (resReal as ObjReal).value, 0.00001)
assertFailsWith<ScriptError> {
eval(
"""
fun square<T: Int | Real>(x: T) = x * x
square("x")
""".trimIndent()
)
}
val reified = eval(
"""
fun sameType<T>(x: T, y: Object) = y is T
sameType(1, "a")
""".trimIndent()
)
assertEquals(false, (reified as ObjBool).value)
}
@Test
fun testFilterBug() = runTest {
eval(

View File

@ -17,8 +17,10 @@
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

@ -1,6 +1,7 @@
package lyng.stdlib
extern fun flow(builder: FlowBuilder.()->Void): Flow
// desired type: FlowBuilder.()->void (function types not yet supported in type grammar)
extern fun flow(builder)
/* Built-in exception type. */
extern class Exception
@ -9,10 +10,10 @@ extern class NotImplementedException
extern class Delegate
// Built-in math helpers (implemented in host runtime).
extern fun abs(x: Object): Real
extern fun ln(x: Object): Real
extern fun pow(x: Object, y: Object): Real
extern fun sqrt(x: Object): Real
extern fun abs(x)
extern fun ln(x)
extern fun pow(x, y)
extern fun sqrt(x)
// Last regex match result, updated by =~ / !~.
var $~ = null
@ -21,7 +22,7 @@ var $~ = null
Wrap a builder into a zero-argument thunk that computes once and caches the result.
The first call invokes builder() and stores the value; subsequent calls return the cached value.
*/
fun cached<T>(builder: ()->T): ()->T {
fun cached(builder) {
var calculated = false
var value = null
{
@ -29,13 +30,13 @@ fun cached<T>(builder: ()->T): ()->T {
value = builder()
calculated = true
}
value as T
value
}
}
/* Filter elements of this iterable using the provided predicate and provide a flow
of results. Coudl be used to map infinte flows, etc.
*/
fun Iterable.filterFlow<T>(predicate: (T)->Bool): Flow<T> {
fun Iterable.filterFlow(predicate): Flow {
val list = this
flow {
for( item in list ) {
@ -49,8 +50,8 @@ fun Iterable.filterFlow<T>(predicate: (T)->Bool): Flow<T> {
/*
Filter this iterable and return List of elements
*/
fun Iterable.filter<T>(predicate: (T)->Bool): List<T> {
var result: List<T> = List()
fun Iterable.filter(predicate) {
var result: List = List()
for( item in this ) if( predicate(item) ) result += item
result
}
@ -58,7 +59,7 @@ fun Iterable.filter<T>(predicate: (T)->Bool): List<T> {
/*
Count all items in this iterable for which predicate returns true
*/
fun Iterable.count<T>(predicate: (T)->Bool): Int {
fun Iterable.count(predicate): Int {
var hits = 0
this.forEach {
if( predicate(it) ) hits++
@ -69,24 +70,24 @@ fun Iterable.count<T>(predicate: (T)->Bool): Int {
filter out all null elements from this collection (Iterable); flow of
non-null elements is returned
*/
fun Iterable.filterFlowNotNull<T>(): Flow<T> {
fun Iterable.filterFlowNotNull(): Flow {
filterFlow { it != null }
}
/* Filter non-null elements and collect them into a List
*/
fun Iterable.filterNotNull<T>(): List<T> {
fun Iterable.filterNotNull(): List {
filter { it != null }
}
/* Skip the first N elements of this iterable. */
fun Iterable.drop<T>(n: Int): List<T> {
fun Iterable.drop(n) {
var cnt = 0
filter { cnt++ >= n }
}
/* Return the first element or throw if the iterable is empty. */
val Iterable.first: Object get() {
val Iterable.first get() {
val i: Iterator = iterator()
if( !i.hasNext() ) throw NoSuchElementException()
i.next().also { i.cancelIteration() }
@ -96,7 +97,7 @@ val Iterable.first: Object get() {
Return the first element that matches the predicate or throws
NuSuchElementException
*/
fun Iterable.findFirst<T>(predicate: (T)->Bool): T {
fun Iterable.findFirst(predicate) {
for( x in this ) {
if( predicate(x) )
break x
@ -107,7 +108,7 @@ fun Iterable.findFirst<T>(predicate: (T)->Bool): T {
/*
return the first element matching the predicate or null
*/
fun Iterable.findFirstOrNull<T>(predicate: (T)->Bool): T? {
fun Iterable.findFirstOrNull(predicate) {
for( x in this ) {
if( predicate(x) )
break x
@ -117,7 +118,7 @@ fun Iterable.findFirstOrNull<T>(predicate: (T)->Bool): T? {
/* Return the last element or throw if the iterable is empty. */
val Iterable.last: Object get() {
val Iterable.last get() {
var found = false
var element = null
for( i in this ) {
@ -129,7 +130,7 @@ val Iterable.last: Object get() {
}
/* Emit all but the last N elements of this iterable. */
fun Iterable.dropLast<T>(n: Int): Flow<T> {
fun Iterable.dropLast(n) {
val list = this
val buffer = RingBuffer(n)
flow {
@ -142,17 +143,17 @@ fun Iterable.dropLast<T>(n: Int): Flow<T> {
}
/* Return the last N elements of this iterable as a buffer/list. */
fun Iterable.takeLast<T>(n: Int): RingBuffer<T> {
val buffer: RingBuffer<T> = RingBuffer(n)
fun Iterable.takeLast(n) {
val buffer = RingBuffer(n)
for( item in this ) buffer += item
buffer
}
/* Join elements into a string with a separator (separator parameter) and optional transformer. */
fun Iterable.joinToString<T>(separator: String=" ", transformer: (T)->Object = { it }): String {
fun Iterable.joinToString(separator=" ", transformer=null) {
var result = null
for( part in this ) {
val transformed = transformer(part).toString()
val transformed = transformer?(part)?.toString() ?: part.toString()
if( result == null ) result = transformed
else result += separator + transformed
}
@ -160,7 +161,7 @@ fun Iterable.joinToString<T>(separator: String=" ", transformer: (T)->Object = {
}
/* Return true if any element matches the predicate. */
fun Iterable.any<T>(predicate: (T)->Bool): Bool {
fun Iterable.any(predicate): Bool {
for( i in this ) {
if( predicate(i) )
break true
@ -168,12 +169,12 @@ fun Iterable.any<T>(predicate: (T)->Bool): Bool {
}
/* Return true if all elements match the predicate. */
fun Iterable.all<T>(predicate: (T)->Bool): Bool {
fun Iterable.all(predicate): Bool {
!any { !predicate(it) }
}
/* Sum all elements; returns null for empty collections. */
fun Iterable.sum<T>(): T? {
fun Iterable.sum() {
val i: Iterator = iterator()
if( i.hasNext() ) {
var result = i.next()
@ -184,7 +185,7 @@ fun Iterable.sum<T>(): T? {
}
/* Sum mapped values of elements; returns null for empty collections. */
fun Iterable.sumOf<T,R>(f: (T)->R): R? {
fun Iterable.sumOf(f) {
val i: Iterator = iterator()
if( i.hasNext() ) {
var result = f(i.next())
@ -195,7 +196,7 @@ fun Iterable.sumOf<T,R>(f: (T)->R): R? {
}
/* Minimum value of the given function applied to elements of the collection. */
fun Iterable.minOf<T,R>(lambda: (T)->R): R {
fun Iterable.minOf( lambda ) {
val i: Iterator = iterator()
var minimum = lambda( i.next() )
while( i.hasNext() ) {
@ -206,7 +207,7 @@ fun Iterable.minOf<T,R>(lambda: (T)->R): R {
}
/* Maximum value of the given function applied to elements of the collection. */
fun Iterable.maxOf<T,R>(lambda: (T)->R): R {
fun Iterable.maxOf( lambda ) {
val i: Iterator = iterator()
var maximum = lambda( i.next() )
while( i.hasNext() ) {
@ -217,18 +218,18 @@ fun Iterable.maxOf<T,R>(lambda: (T)->R): R {
}
/* Return elements sorted by natural order. */
fun Iterable.sorted<T>(): List<T> {
fun Iterable.sorted() {
sortedWith { a, b -> a <=> b }
}
/* Return elements sorted by the key selector. */
fun Iterable.sortedBy<T,R>(predicate: (T)->R): List<T> {
fun Iterable.sortedBy(predicate) {
sortedWith { a, b -> predicate(a) <=> predicate(b) }
}
/* Return a shuffled copy of the iterable as a list. */
fun Iterable.shuffled<T>(): List<T> {
val list: List<T> = toList()
fun Iterable.shuffled() {
val list: List = toList
list.shuffle()
list
}
@ -237,8 +238,8 @@ fun Iterable.shuffled<T>(): List<T> {
Returns a single list of all elements from all collections in the given collection.
@return List
*/
fun Iterable.flatten<T>(): List<T> {
var result: List<T> = List()
fun Iterable.flatten() {
var result: List = List()
forEach { i ->
i.forEach { result += it }
}
@ -249,8 +250,8 @@ fun Iterable.flatten<T>(): List<T> {
Returns a single list of all elements yielded from results of transform function being
invoked on each element of original collection.
*/
fun Iterable.flatMap<T,R>(transform: (T)->Iterable<R>): List<R> {
val mapped: List<Iterable<R>> = map(transform)
fun Iterable.flatMap(transform): List {
val mapped: List = map(transform)
mapped.flatten()
}
@ -267,26 +268,26 @@ override fun List.toString() {
}
/* Sort list in-place by key selector. */
fun List.sortBy<T,R>(predicate: (T)->R): Void {
fun List.sortBy(predicate) {
sortWith { a, b -> predicate(a) <=> predicate(b) }
}
/* Sort list in-place by natural order. */
fun List.sort<T>(): Void {
fun List.sort() {
sortWith { a, b -> a <=> b }
}
/* Print this exception and its stack trace to standard output. */
fun Exception.printStackTrace(): Void {
fun Exception.printStackTrace() {
println(this)
for( entry in stackTrace )
println("\tat "+entry.toString())
}
/* Compile this string into a regular expression. */
val String.re: Regex get() = Regex(this)
val String.re get() = Regex(this)
fun TODO(message: Object?=null): Void {
fun TODO(message=null) {
throw "not implemented"
}
@ -305,31 +306,31 @@ enum DelegateAccess {
Implementing this interface is optional as Lyng uses dynamic dispatch,
but it is recommended for documentation and clarity.
*/
interface Delegate<T,ThisRefType=Void> {
interface Delegate {
/* Called when a delegated 'val' or 'var' is read. */
fun getValue(thisRef: ThisRefType, name: String): T = TODO("delegate getter is not implemented")
fun getValue(thisRef, name) = TODO("delegate getter is not implemented")
/* Called when a delegated 'var' is written. */
fun setValue(thisRef: ThisRefType, name: String, newValue: T): Void = TODO("delegate setter is not implemented")
fun setValue(thisRef, name, newValue) = TODO("delegate setter is not implemented")
/* Called when a delegated function is invoked. */
fun invoke(thisRef: ThisRefType, name: String, args...): Object = TODO("delegate invoke is not implemented")
fun invoke(thisRef, name, args...) = TODO("delegate invoke is not implemented")
/*
Called once during initialization to configure or validate the delegate.
Should return the delegate object to be used (usually 'this').
*/
fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object = this
fun bind(name, access, thisRef) = this
}
/*
Executes the block with `this` set to self and
returns what the block returns.
*/
fun with<T,R>(self: T, block: T.()->R): R {
fun with(self, block) {
var result = Unset
self.apply { result = block() }
result as R
result
}
/*
@ -337,19 +338,19 @@ fun with<T,R>(self: T, block: T.()->R): R {
The provided creator lambda is called once on the first access to compute the value.
Can only be used with 'val' properties.
*/
class lazy<T>(creatorParam: Object.()->T) : Delegate<T,Object> {
private val creator: Object.()->T = creatorParam
class lazy(creatorParam) : Delegate {
private val creator = creatorParam
private var value = Unset
override fun bind(name: String, access: DelegateAccess, thisRef: Object): Object {
override fun bind(name, access, thisRef) {
if (access.toString() != "DelegateAccess.Val") throw "lazy delegate can only be used with 'val'"
this
}
override fun getValue(thisRef: Object, name: String): T {
override fun getValue(thisRef, name) {
if (value == Unset)
value = with(thisRef,creator)
value as T
value
}
}

View File

@ -4,7 +4,7 @@ better than even in C++ ;)
```lyng
fun t(x) {
// x is Object (non-null)
// x is Object, and x is nullable
println(x)
}
@ -117,10 +117,6 @@ 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:
@ -203,22 +199,12 @@ square("3.14")
- Generics runtime model: Are type params reified via hidden Class args always, or only when used (T::class, T is ...)? How does this interact with Kotlin interop?
Type params are erased by default. Hidden `Class` args are only injected when a type parameter is used in a reified way (`T::class`, `T is`, `is T`, `as T`) or when the class has at least one `extern` symbol (so host implementations can rely on them). Otherwise `T` is compile-time only and runtime uses `Object`.
- Variance syntax:
- Declaration-site only, Kotlin-style: `out` (covariant) and `in` (contravariant).
- Example: `class Box<out T>`, `class Sink<in T>`.
- Bounds remain `T: A & B` or `T: A | B`.
I think we can omit if not used. For kotlin interop: if the class has at least one `extern` symbol, that means native implementation, we always include type parameters, to kotlin implementation can rely on it.
- Member access rules: If a variable is Object (dynamic), is member access a compile-time error, or allowed with fallback (which we are trying to remove)? If error, do we require explicit cast first?
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