Compare commits

...

3 Commits

12 changed files with 772 additions and 124 deletions

View File

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

View File

@ -64,12 +64,38 @@ class Compiler(
) )
private val slotPlanStack = mutableListOf<SlotPlan>() private val slotPlanStack = mutableListOf<SlotPlan>()
private var nextScopeId = 0 private var nextScopeId = 0
private val genericFunctionDeclsStack = mutableListOf<MutableMap<String, GenericFunctionDecl>>(mutableMapOf())
// Track declared local variables count per function for precise capacity hints // Track declared local variables count per function for precise capacity hints
private val localDeclCountStack = mutableListOf<Int>() private val localDeclCountStack = mutableListOf<Int>()
private val currentLocalDeclCount: Int private val currentLocalDeclCount: Int
get() = localDeclCountStack.lastOrNull() ?: 0 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 { private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T {
localNamesStack.add(names.toMutableSet()) localNamesStack.add(names.toMutableSet())
return try { return try {
@ -103,6 +129,8 @@ class Compiler(
} }
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull() private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
@ -112,6 +140,36 @@ class Compiler(
if (!record.visibility.isPublic) continue if (!record.visibility.isPublic) continue
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated) declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
} }
for ((cls, map) in current.extensions) {
for ((name, record) in map) {
if (!record.visibility.isPublic) continue
when (record.type) {
ObjRecord.Type.Property -> {
declareSlotNameIn(
plan,
extensionPropertyGetterName(cls.className, name),
isMutable = false,
isDelegated = false
)
val prop = record.value as? ObjProperty
if (prop?.setter != null) {
declareSlotNameIn(
plan,
extensionPropertySetterName(cls.className, name),
isMutable = false,
isDelegated = false
)
}
}
else -> declareSlotNameIn(
plan,
extensionCallableName(cls.className, name),
isMutable = false,
isDelegated = false
)
}
}
}
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) { for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
val record = current.getSlotRecord(slotIndex) val record = current.getSlotRecord(slotIndex)
if (!record.visibility.isPublic) continue if (!record.visibility.isPublic) continue
@ -126,6 +184,8 @@ class Compiler(
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
val saved = cc.savePos() val saved = cc.savePos()
var depth = 0 var depth = 0
var parenDepth = 0
var bracketDepth = 0
fun nextNonWs(): Token { fun nextNonWs(): Token {
var t = cc.next() var t = cc.next()
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) { while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
@ -139,7 +199,12 @@ class Compiler(
when (t.type) { when (t.type) {
Token.Type.LBRACE -> depth++ Token.Type.LBRACE -> depth++
Token.Type.RBRACE -> if (depth > 0) depth-- Token.Type.RBRACE -> if (depth > 0) depth--
Token.Type.LPAREN -> parenDepth++
Token.Type.RPAREN -> if (parenDepth > 0) parenDepth--
Token.Type.LBRACKET -> bracketDepth++
Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth--
Token.Type.ID -> if (depth == 0) { Token.Type.ID -> if (depth == 0) {
if (parenDepth > 0 || bracketDepth > 0) continue
when (t.value) { when (t.value) {
"fun", "fn" -> { "fun", "fn" -> {
val nameToken = nextNonWs() val nameToken = nextNonWs()
@ -399,6 +464,62 @@ class Compiler(
return null 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? { private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? {
for (i in slotPlanStack.indices.reversed()) { for (i in slotPlanStack.indices.reversed()) {
if (!includeModule && i == 0) continue if (!includeModule && i == 0) continue
@ -468,6 +589,15 @@ class Compiler(
val ids = resolveMemberIds(name, pos, null) val ids = resolveMemberIds(name, pos, null)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName()) return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
} }
val implicitThisMembers = codeContexts.any { ctx ->
(ctx as? CodeContext.Function)?.implicitThisMembers == true
}
val implicitType = if (implicitThisMembers) currentImplicitThisTypeName() else null
if (implicitType != null && hasImplicitThisMember(name, implicitType)) {
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
}
val modulePlan = moduleSlotPlan() val modulePlan = moduleSlotPlan()
val moduleEntry = modulePlan?.slots?.get(name) val moduleEntry = modulePlan?.slots?.get(name)
if (moduleEntry != null) { if (moduleEntry != null) {
@ -514,15 +644,6 @@ class Compiler(
return ref return ref
} }
} }
val implicitThis = codeContexts.any { ctx ->
(ctx as? CodeContext.Function)?.implicitThisMembers == true
}
if (implicitThis) {
val implicitType = currentImplicitThisTypeName()
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
}
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody } val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
if (classContext && extensionNames.contains(name)) { if (classContext && extensionNames.contains(name)) {
resolutionSink?.referenceMember(name, pos) resolutionSink?.referenceMember(name, pos)
@ -682,6 +803,7 @@ class Compiler(
private suspend fun <T> inCodeContext(context: CodeContext, f: suspend () -> T): T { private suspend fun <T> inCodeContext(context: CodeContext, f: suspend () -> T): T {
codeContexts.add(context) codeContexts.add(context)
pushGenericFunctionScope()
try { try {
val res = f() val res = f()
if (context is CodeContext.ClassBody) { if (context is CodeContext.ClassBody) {
@ -692,6 +814,7 @@ class Compiler(
} }
return res return res
} finally { } finally {
popGenericFunctionScope()
codeContexts.removeLast() codeContexts.removeLast()
} }
} }
@ -915,9 +1038,16 @@ class Compiler(
private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds { private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds {
if (implicitTypeName == null) return resolveMemberIds(name, pos, null) if (implicitTypeName == null) return resolveMemberIds(name, pos, null)
val info = resolveCompileClassInfo(implicitTypeName) val info = resolveCompileClassInfo(implicitTypeName)
?: if (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $implicitTypeName") val fieldId = info?.fieldIds?.get(name)
val fieldId = info.fieldIds[name] val methodId = info?.methodIds?.get(name)
val methodId = info.methodIds[name] if (fieldId == null && methodId == null) {
val cls = resolveClassByName(implicitTypeName)
if (cls != null) {
val fId = cls.instanceFieldIdMap()[name]
val mId = cls.instanceMethodIdMap(includeAbstract = true)[name]
if (fId != null || mId != null) return MemberIds(fId, mId)
}
}
if (fieldId == null && methodId == null) { if (fieldId == null && methodId == null) {
if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null) if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null)
if (allowUnresolvedRefs) return MemberIds(null, null) if (allowUnresolvedRefs) return MemberIds(null, null)
@ -925,6 +1055,19 @@ class Compiler(
} }
return MemberIds(fieldId, methodId) return MemberIds(fieldId, methodId)
} }
private fun hasImplicitThisMember(name: String, implicitTypeName: String?): Boolean {
if (implicitTypeName == null) return false
val info = resolveCompileClassInfo(implicitTypeName)
if (info != null && (info.fieldIds.containsKey(name) || info.methodIds.containsKey(name))) return true
val cls = resolveClassByName(implicitTypeName)
if (cls != null) {
if (cls.instanceFieldIdMap().containsKey(name) || cls.instanceMethodIdMap(includeAbstract = true).containsKey(name)) {
return true
}
}
return hasExtensionFor(implicitTypeName, name)
}
private val currentRangeParamNames: Set<String> private val currentRangeParamNames: Set<String>
get() = rangeParamNamesStack.lastOrNull() ?: emptySet() get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
private val capturePlanStack = mutableListOf<CapturePlan>() private val capturePlanStack = mutableListOf<CapturePlan>()
@ -1443,10 +1586,19 @@ class Compiler(
break 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) val rvalue = parseExpressionLevel(level + 1)
?: throw ScriptError(opToken.pos, "Expecting expression") ?: throw ScriptError(opToken.pos, "Expecting expression")
op.generate(opToken.pos, lvalue!!, rvalue)
val res = op.generate(opToken.pos, lvalue!!, rvalue) }
if (opToken.type == Token.Type.ASSIGN) { if (opToken.type == Token.Type.ASSIGN) {
val ctx = codeContexts.lastOrNull() val ctx = codeContexts.lastOrNull()
if (ctx is CodeContext.ClassBody) { if (ctx is CodeContext.ClassBody) {
@ -1527,7 +1679,10 @@ class Compiler(
Token.Type.LPAREN -> { Token.Type.LPAREN -> {
cc.next() cc.next()
// instance method call // instance method call
val parsed = parseArgs() val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val parsed = parseArgs(receiverType)
val args = parsed.first val args = parsed.first
val tailBlock = parsed.second val tailBlock = parsed.second
if (left is LocalVarRef && left.name == "scope") { if (left is LocalVarRef && left.name == "scope") {
@ -1578,7 +1733,10 @@ class Compiler(
// single lambda arg, like assertThrows { ... } // single lambda arg, like assertThrows { ... }
cc.next() cc.next()
isCall = true isCall = true
val lambda = parseLambdaExpression() val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val lambda = parseLambdaExpression(receiverType)
val argPos = next.pos val argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos)) val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
operand = when (left) { operand = when (left) {
@ -2274,6 +2432,119 @@ class Compiler(
} }
private fun parseTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> { 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)* // Parse a qualified base name: ID ('.' ID)*
val segments = mutableListOf<MiniTypeName.Segment>() val segments = mutableListOf<MiniTypeName.Segment>()
var first = true var first = true
@ -2295,7 +2566,11 @@ class Compiler(
val dotPos = cc.savePos() val dotPos = cc.savePos()
val t = cc.next() val t = cc.next()
if (t.type == Token.Type.DOT) { if (t.type == Token.Type.DOT) {
// continue val nextAfterDot = cc.peekNextNonWhitespace()
if (nextAfterDot.type == Token.Type.LPAREN) {
cc.restorePos(dotPos)
break
}
continue continue
} else { } else {
cc.restorePos(dotPos) cc.restorePos(dotPos)
@ -2304,6 +2579,18 @@ class Compiler(
} }
val qualified = segments.joinToString(".") { it.name } val qualified = segments.joinToString(".") { it.name }
val typeParams = currentTypeParams()
if (segments.size == 1 && typeParams.contains(qualified)) {
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
true
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
cc.pushPendingAssign()
true
} else false
val rangeEnd = cc.currentPos()
val miniRef = MiniTypeVar(MiniRange(typeStart, rangeEnd), qualified, isNullable)
return TypeDecl.TypeVar(qualified, isNullable) to miniRef
}
if (segments.size > 1) { if (segments.size > 1) {
lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) } lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) }
} else { } else {
@ -2366,6 +2653,126 @@ class Compiler(
return Pair(sem, miniRef) return Pair(sem, miniRef)
} }
private fun typeDeclToTypeRef(typeDecl: TypeDecl, pos: Pos): ObjRef {
return when (typeDecl) {
TypeDecl.TypeAny,
TypeDecl.TypeNullableAny -> 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 * Parse arguments list during the call and detect last block argument
* _following the parenthesis_ call: `(1,2) { ... }` * _following the parenthesis_ call: `(1,2) { ... }`
@ -2502,6 +2909,11 @@ class Compiler(
): ObjRef { ): ObjRef {
var detectedBlockArgument = blockArgument var detectedBlockArgument = blockArgument
val expectedReceiver = tailBlockReceiverType(left) val expectedReceiver = tailBlockReceiverType(left)
val withReceiver = when (left) {
is LocalVarRef -> if (left.name == "with") left.name else null
is LocalSlotRef -> if (left.name == "with") left.name else null
else -> null
}
val args = if (blockArgument) { val args = if (blockArgument) {
// Leading '{' has already been consumed by the caller token branch. // Leading '{' has already been consumed by the caller token branch.
// Parse only the lambda expression as the last argument and DO NOT // Parse only the lambda expression as the last argument and DO NOT
@ -2510,11 +2922,26 @@ class Compiler(
// foo { ... }.bar() == (foo { ... }).bar() // foo { ... }.bar() == (foo { ... }).bar()
val callableAccessor = parseLambdaExpression(expectedReceiver) val callableAccessor = parseLambdaExpression(expectedReceiver)
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos())) listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
} else {
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 { } else {
val r = parseArgs(expectedReceiver) val r = parseArgs(expectedReceiver)
detectedBlockArgument = r.second detectedBlockArgument = r.second
r.first r.first
} }
}
val implicitThisTypeName = currentImplicitThisTypeName() val implicitThisTypeName = currentImplicitThisTypeName()
return when (left) { return when (left) {
is ImplicitThisMemberRef -> is ImplicitThisMemberRef ->
@ -2544,6 +2971,7 @@ class Compiler(
implicitThisTypeName implicitThisTypeName
) )
} else { } else {
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional) CallRef(left, args, detectedBlockArgument, isOptional)
} }
} }
@ -2564,6 +2992,7 @@ class Compiler(
implicitThisTypeName implicitThisTypeName
) )
} else { } else {
checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional) 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? { private suspend fun parseAccessor(): ObjRef? {
// could be: literal // could be: literal
val t = cc.next() val t = cc.next()
@ -3443,10 +3892,20 @@ class Compiler(
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos) resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) { return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) {
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
val constructorArgsDeclaration = 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)) if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true) parseArgsDeclaration(isClassDeclaration = true)
else ArgsDeclaration(emptyList(), Token.Type.RPAREN) else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
} finally {
pendingTypeParamStack.removeLast()
}
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN) if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError( throw ScriptError(
@ -3470,19 +3929,28 @@ class Compiler(
data class BaseSpec(val name: String, val args: List<ParsedArgument>?) data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
val baseSpecs = mutableListOf<BaseSpec>() val baseSpecs = mutableListOf<BaseSpec>()
pendingTypeParamStack.add(classTypeParams)
try {
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) { if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
do { do {
val baseId = cc.requireToken(Token.Type.ID, "base class name expected") val (baseDecl, _) = parseSimpleTypeExpressionWithMini()
resolutionSink?.reference(baseId.value, baseId.pos) 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 var argsList: List<ParsedArgument>? = null
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens // Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) { if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
// Parse args without consuming any following block so that a class body can follow safely // Parse args without consuming any following block so that a class body can follow safely
argsList = parseArgsNoTailBlock() argsList = parseArgsNoTailBlock()
} }
baseSpecs += BaseSpec(baseId.value, argsList) baseSpecs += BaseSpec(baseName, argsList)
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true)) } while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
} }
} finally {
pendingTypeParamStack.removeLast()
}
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
@ -3597,6 +4065,40 @@ class Compiler(
miniSink?.onClassDecl(node) miniSink?.onClassDecl(node)
} }
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos) resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
classCtx?.let { ctx ->
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
ctx.memberFieldIds.putAll(baseIds.fieldIds)
ctx.memberMethodIds.putAll(baseIds.methodIds)
ctx.nextFieldId = maxOf(ctx.nextFieldId, baseIds.nextFieldId)
ctx.nextMethodId = maxOf(ctx.nextMethodId, baseIds.nextMethodId)
for (member in ctx.declaredMembers) {
val isOverride = ctx.memberOverrides[member] == true
val hasBaseField = member in baseIds.fieldIds
val hasBaseMethod = member in baseIds.methodIds
if (isOverride) {
if (!hasBaseField && !hasBaseMethod) {
throw ScriptError(nameToken.pos, "member $member is marked 'override' but does not override anything")
}
} else {
if (hasBaseField || hasBaseMethod) {
throw ScriptError(nameToken.pos, "member $member overrides parent member but 'override' keyword is missing")
}
}
if (!ctx.memberFieldIds.containsKey(member)) {
ctx.memberFieldIds[member] = ctx.nextFieldId++
}
if (!ctx.memberMethodIds.containsKey(member)) {
ctx.memberMethodIds[member] = ctx.nextMethodId++
}
}
compileClassInfos[nameToken.value] = CompileClassInfo(
name = nameToken.value,
fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
nextMethodId = ctx.nextMethodId
)
}
// restore if no body starts here // restore if no body starts here
cc.restorePos(saved) cc.restorePos(saved)
null null
@ -4067,16 +4569,29 @@ class Compiler(
declareLocalName(extensionWrapperName, isMutable = false) declareLocalName(extensionWrapperName, isMutable = false)
} }
val argsDeclaration: ArgsDeclaration = 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) { if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
cc.nextNonWhitespace() // consume ( cc.nextNonWhitespace() // consume (
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN) parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
} else 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 // Optional return type
val returnTypeMini: MiniTypeRef? = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) { returnTypeMini = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
parseTypeDeclarationWithMini().second parseTypeDeclarationWithMini().second
} else null } else null
} finally {
pendingTypeParamStack.removeLast()
}
var isDelegated = false var isDelegated = false
var delegateExpression: Statement? = null var delegateExpression: Statement? = null
@ -4128,15 +4643,18 @@ class Compiler(
CodeContext.Function( CodeContext.Function(
name, name,
implicitThisMembers = implicitThisMembers, implicitThisMembers = implicitThisMembers,
implicitThisTypeName = extTypeName implicitThisTypeName = extTypeName,
typeParams = typeParams,
typeParamDecls = typeParamDecls
) )
) { ) {
cc.labels.add(name) cc.labels.add(name)
outerLabel?.let { cc.labels.add(it) } outerLabel?.let { cc.labels.add(it) }
val paramNamesList = argsDeclaration.params.map { it.name } val paramNamesList = argsDeclaration.params.map { it.name }
val typeParamNames = typeParamDecls.map { it.name }
val paramNames: Set<String> = paramNamesList.toSet() val paramNames: Set<String> = paramNamesList.toSet()
val paramSlotPlan = buildParamSlotPlan(paramNamesList) val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames)
val capturePlan = CapturePlan(paramSlotPlan) val capturePlan = CapturePlan(paramSlotPlan)
val rangeParamNames = argsDeclaration.params val rangeParamNames = argsDeclaration.params
.filter { isRangeType(it.type) } .filter { isRangeType(it.type) }
@ -4238,6 +4756,7 @@ class Compiler(
// load params from caller context // load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
bindTypeParamsAtRuntime(context, argsDeclaration, typeParamDecls)
if (extTypeName != null) { if (extTypeName != null) {
context.thisObj = callerContext.thisObj context.thisObj = callerContext.thisObj
} }
@ -4545,6 +5064,10 @@ class Compiler(
val rawName = when (type) { val rawName = when (type) {
is TypeDecl.Simple -> type.name is TypeDecl.Simple -> type.name
is TypeDecl.Generic -> type.name is TypeDecl.Generic -> type.name
is TypeDecl.Function -> "Callable"
is TypeDecl.TypeVar -> return null
is TypeDecl.Union -> return null
is TypeDecl.Intersection -> return null
else -> return null else -> return null
} }
val name = rawName.substringAfterLast('.') val name = rawName.substringAfterLast('.')
@ -4986,6 +5509,12 @@ class Compiler(
val slotIndex = slotPlan?.slots?.get(name)?.index val slotIndex = slotPlan?.slots?.get(name)?.index
val scopeId = slotPlan?.id val scopeId = slotPlan?.id
val initObjClass = resolveInitializerObjClass(initialExpression) ?: resolveTypeDeclObjClass(varTypeDecl) val initObjClass = resolveInitializerObjClass(initialExpression) ?: resolveTypeDeclObjClass(varTypeDecl)
if (initObjClass != null) {
if (slotIndex != null && scopeId != null) {
slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass
}
nameObjClass[name] = initObjClass
}
return VarDeclStatement( return VarDeclStatement(
name, name,
isMutable, isMutable,

View File

@ -22,8 +22,23 @@ package net.sergeych.lyng
// this is highly experimental and subject to complete redesign // this is highly experimental and subject to complete redesign
// very soon // very soon
sealed class TypeDecl(val isNullable:Boolean = false) { 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 TypeAny : TypeDecl()
object TypeNullableAny : TypeDecl(true) object TypeNullableAny : TypeDecl(true)

View File

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

View File

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

View File

@ -150,6 +150,18 @@ data class MiniTypeVar(
val nullable: Boolean val nullable: Boolean
) : MiniTypeRef ) : 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) // Script and declarations (lean subset; can be extended later)
sealed interface MiniNamedDecl : MiniNode { sealed interface MiniNamedDecl : MiniNode {
val name: String val name: String

View File

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

View File

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

View File

@ -5273,6 +5273,40 @@ class ScriptTest {
assertEquals(ObjFalse, scope.eval("isInt(\"42\")")) 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 @Test
fun testFilterBug() = runTest { fun testFilterBug() = runTest {
eval( eval(

View File

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

View File

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

View File

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