Compare commits

...

3 Commits

12 changed files with 772 additions and 124 deletions

View File

@ -22,9 +22,13 @@ 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(),
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
): 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,12 +64,38 @@ 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 {
@ -103,6 +129,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 +140,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 +184,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 +199,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 +464,62 @@ 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
@ -468,6 +589,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 +644,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)
@ -682,6 +803,7 @@ 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) {
@ -692,6 +814,7 @@ class Compiler(
}
return res
} finally {
popGenericFunctionScope()
codeContexts.removeLast()
}
}
@ -915,9 +1038,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 +1055,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 +1586,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 +1679,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 +1733,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 +2432,119 @@ 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
@ -2295,7 +2566,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 +2579,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 +2653,126 @@ 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) { ... }`
@ -2502,6 +2909,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 +2923,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) {
@ -2544,6 +2971,7 @@ class Compiler(
implicitThisTypeName
)
} else {
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional)
}
}
@ -2564,6 +2992,7 @@ class Compiler(
implicitThisTypeName
)
} else {
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional)
}
}
@ -2571,6 +3000,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()
@ -3443,10 +3892,20 @@ 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 constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true)
else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
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()
}
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError(
@ -3470,18 +3929,27 @@ class Compiler(
data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
val baseSpecs = mutableListOf<BaseSpec>()
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))
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()
}
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
@ -3597,6 +4065,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,16 +4569,29 @@ class Compiler(
declareLocalName(extensionWrapperName, isMutable = false)
}
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)
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)
// Optional return type
val returnTypeMini: MiniTypeRef? = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
parseTypeDeclarationWithMini().second
} else null
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()
}
var isDelegated = false
var delegateExpression: Statement? = null
@ -4128,15 +4643,18 @@ class Compiler(
CodeContext.Function(
name,
implicitThisMembers = implicitThisMembers,
implicitThisTypeName = extTypeName
implicitThisTypeName = extTypeName,
typeParams = typeParams,
typeParamDecls = typeParamDecls
)
) {
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)
val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames)
val capturePlan = CapturePlan(paramSlotPlan)
val rangeParamNames = argsDeclaration.params
.filter { isRangeType(it.type) }
@ -4238,6 +4756,7 @@ 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
}
@ -4545,6 +5064,10 @@ 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('.')
@ -4986,6 +5509,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

@ -22,8 +22,23 @@ 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 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)
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
)
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

@ -1025,6 +1025,8 @@ 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) {
@ -1035,6 +1037,8 @@ 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,6 +150,18 @@ 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,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

@ -5273,6 +5273,40 @@ 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,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

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

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:
@ -199,12 +203,22 @@ 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?
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.
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`.
- 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