Add minimal generics and typed callable casts
This commit is contained in:
parent
51b397686d
commit
c9da0b256f
@ -22,7 +22,8 @@ sealed class CodeContext {
|
||||
class Function(
|
||||
val name: String,
|
||||
val implicitThisMembers: Boolean = false,
|
||||
val implicitThisTypeName: String? = null
|
||||
val implicitThisTypeName: String? = null,
|
||||
val typeParams: Set<String> = emptySet()
|
||||
): CodeContext()
|
||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||
|
||||
@ -103,6 +103,8 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
|
||||
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
|
||||
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
@ -112,6 +114,36 @@ class Compiler(
|
||||
if (!record.visibility.isPublic) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
}
|
||||
for ((cls, map) in current.extensions) {
|
||||
for ((name, record) in map) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
when (record.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
extensionPropertyGetterName(cls.className, name),
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
val prop = record.value as? ObjProperty
|
||||
if (prop?.setter != null) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
extensionPropertySetterName(cls.className, name),
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> declareSlotNameIn(
|
||||
plan,
|
||||
extensionCallableName(cls.className, name),
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
|
||||
val record = current.getSlotRecord(slotIndex)
|
||||
if (!record.visibility.isPublic) continue
|
||||
@ -126,6 +158,8 @@ class Compiler(
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
val saved = cc.savePos()
|
||||
var depth = 0
|
||||
var parenDepth = 0
|
||||
var bracketDepth = 0
|
||||
fun nextNonWs(): Token {
|
||||
var t = cc.next()
|
||||
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
||||
@ -139,7 +173,12 @@ class Compiler(
|
||||
when (t.type) {
|
||||
Token.Type.LBRACE -> depth++
|
||||
Token.Type.RBRACE -> if (depth > 0) depth--
|
||||
Token.Type.LPAREN -> parenDepth++
|
||||
Token.Type.RPAREN -> if (parenDepth > 0) parenDepth--
|
||||
Token.Type.LBRACKET -> bracketDepth++
|
||||
Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth--
|
||||
Token.Type.ID -> if (depth == 0) {
|
||||
if (parenDepth > 0 || bracketDepth > 0) continue
|
||||
when (t.value) {
|
||||
"fun", "fn" -> {
|
||||
val nameToken = nextNonWs()
|
||||
@ -399,6 +438,14 @@ class Compiler(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun currentTypeParams(): Set<String> {
|
||||
for (ctx in codeContexts.asReversed()) {
|
||||
val fn = ctx as? CodeContext.Function ?: continue
|
||||
if (fn.typeParams.isNotEmpty()) return fn.typeParams
|
||||
}
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? {
|
||||
for (i in slotPlanStack.indices.reversed()) {
|
||||
if (!includeModule && i == 0) continue
|
||||
@ -468,6 +515,15 @@ class Compiler(
|
||||
val ids = resolveMemberIds(name, pos, null)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
|
||||
}
|
||||
val implicitThisMembers = codeContexts.any { ctx ->
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
val implicitType = if (implicitThisMembers) currentImplicitThisTypeName() else null
|
||||
if (implicitType != null && hasImplicitThisMember(name, implicitType)) {
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
|
||||
}
|
||||
val modulePlan = moduleSlotPlan()
|
||||
val moduleEntry = modulePlan?.slots?.get(name)
|
||||
if (moduleEntry != null) {
|
||||
@ -514,15 +570,6 @@ class Compiler(
|
||||
return ref
|
||||
}
|
||||
}
|
||||
val implicitThis = codeContexts.any { ctx ->
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
if (implicitThis) {
|
||||
val implicitType = currentImplicitThisTypeName()
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
|
||||
}
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
if (classContext && extensionNames.contains(name)) {
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
@ -915,9 +962,16 @@ class Compiler(
|
||||
private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds {
|
||||
if (implicitTypeName == null) return resolveMemberIds(name, pos, null)
|
||||
val info = resolveCompileClassInfo(implicitTypeName)
|
||||
?: if (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $implicitTypeName")
|
||||
val fieldId = info.fieldIds[name]
|
||||
val methodId = info.methodIds[name]
|
||||
val fieldId = info?.fieldIds?.get(name)
|
||||
val methodId = info?.methodIds?.get(name)
|
||||
if (fieldId == null && methodId == null) {
|
||||
val cls = resolveClassByName(implicitTypeName)
|
||||
if (cls != null) {
|
||||
val fId = cls.instanceFieldIdMap()[name]
|
||||
val mId = cls.instanceMethodIdMap(includeAbstract = true)[name]
|
||||
if (fId != null || mId != null) return MemberIds(fId, mId)
|
||||
}
|
||||
}
|
||||
if (fieldId == null && methodId == null) {
|
||||
if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null)
|
||||
if (allowUnresolvedRefs) return MemberIds(null, null)
|
||||
@ -925,6 +979,19 @@ class Compiler(
|
||||
}
|
||||
return MemberIds(fieldId, methodId)
|
||||
}
|
||||
|
||||
private fun hasImplicitThisMember(name: String, implicitTypeName: String?): Boolean {
|
||||
if (implicitTypeName == null) return false
|
||||
val info = resolveCompileClassInfo(implicitTypeName)
|
||||
if (info != null && (info.fieldIds.containsKey(name) || info.methodIds.containsKey(name))) return true
|
||||
val cls = resolveClassByName(implicitTypeName)
|
||||
if (cls != null) {
|
||||
if (cls.instanceFieldIdMap().containsKey(name) || cls.instanceMethodIdMap(includeAbstract = true).containsKey(name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return hasExtensionFor(implicitTypeName, name)
|
||||
}
|
||||
private val currentRangeParamNames: Set<String>
|
||||
get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
|
||||
private val capturePlanStack = mutableListOf<CapturePlan>()
|
||||
@ -1443,10 +1510,19 @@ class Compiler(
|
||||
break
|
||||
}
|
||||
|
||||
val 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")
|
||||
|
||||
val res = op.generate(opToken.pos, lvalue!!, rvalue)
|
||||
op.generate(opToken.pos, lvalue!!, rvalue)
|
||||
}
|
||||
if (opToken.type == Token.Type.ASSIGN) {
|
||||
val ctx = codeContexts.lastOrNull()
|
||||
if (ctx is CodeContext.ClassBody) {
|
||||
@ -1527,7 +1603,10 @@ class Compiler(
|
||||
Token.Type.LPAREN -> {
|
||||
cc.next()
|
||||
// instance method call
|
||||
val parsed = parseArgs()
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val parsed = parseArgs(receiverType)
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
if (left is LocalVarRef && left.name == "scope") {
|
||||
@ -1578,7 +1657,10 @@ class Compiler(
|
||||
// single lambda arg, like assertThrows { ... }
|
||||
cc.next()
|
||||
isCall = true
|
||||
val lambda = parseLambdaExpression()
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val lambda = parseLambdaExpression(receiverType)
|
||||
val argPos = next.pos
|
||||
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
|
||||
operand = when (left) {
|
||||
@ -2274,6 +2356,89 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun parseTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
parseFunctionTypeWithMini()?.let { return it }
|
||||
return parseSimpleTypeExpressionWithMini()
|
||||
}
|
||||
|
||||
private fun parseFunctionTypeWithMini(): Pair<TypeDecl, MiniTypeRef>? {
|
||||
val saved = cc.savePos()
|
||||
val startPos = cc.currentPos()
|
||||
|
||||
fun parseParamTypes(): List<Pair<TypeDecl, MiniTypeRef>> {
|
||||
val params = mutableListOf<Pair<TypeDecl, MiniTypeRef>>()
|
||||
cc.skipWsTokens()
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.RPAREN) {
|
||||
cc.nextNonWhitespace()
|
||||
return params
|
||||
}
|
||||
while (true) {
|
||||
val (paramDecl, paramMini) = parseTypeExpressionWithMini()
|
||||
params += paramDecl to paramMini
|
||||
val sep = cc.nextNonWhitespace()
|
||||
when (sep.type) {
|
||||
Token.Type.COMMA -> continue
|
||||
Token.Type.RPAREN -> return params
|
||||
else -> sep.raiseSyntax("expected ',' or ')' in function type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var receiverDecl: TypeDecl? = null
|
||||
var receiverMini: MiniTypeRef? = null
|
||||
|
||||
val first = cc.peekNextNonWhitespace()
|
||||
if (first.type == Token.Type.LPAREN) {
|
||||
cc.nextNonWhitespace()
|
||||
} else {
|
||||
val recv = parseSimpleTypeExpressionWithMini()
|
||||
val dotPos = cc.savePos()
|
||||
if (cc.skipTokenOfType(Token.Type.DOT, isOptional = true) && cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
receiverDecl = recv.first
|
||||
receiverMini = recv.second
|
||||
cc.nextNonWhitespace()
|
||||
} else {
|
||||
cc.restorePos(saved)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val params = parseParamTypes()
|
||||
val arrow = cc.nextNonWhitespace()
|
||||
if (arrow.type != Token.Type.ARROW) {
|
||||
cc.restorePos(saved)
|
||||
return null
|
||||
}
|
||||
val (retDecl, retMini) = parseTypeExpressionWithMini()
|
||||
|
||||
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
|
||||
true
|
||||
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
|
||||
cc.pushPendingAssign()
|
||||
true
|
||||
} else false
|
||||
|
||||
val rangeStart = when (receiverMini) {
|
||||
null -> startPos
|
||||
else -> receiverMini.range.start
|
||||
}
|
||||
val rangeEnd = cc.currentPos()
|
||||
val mini = MiniFunctionType(
|
||||
range = MiniRange(rangeStart, rangeEnd),
|
||||
receiver = receiverMini,
|
||||
params = params.map { it.second },
|
||||
returnType = retMini,
|
||||
nullable = isNullable
|
||||
)
|
||||
val sem = TypeDecl.Function(
|
||||
receiver = receiverDecl,
|
||||
params = params.map { it.first },
|
||||
returnType = retDecl,
|
||||
nullable = isNullable
|
||||
)
|
||||
return sem to mini
|
||||
}
|
||||
|
||||
private fun parseSimpleTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
// Parse a qualified base name: ID ('.' ID)*
|
||||
val segments = mutableListOf<MiniTypeName.Segment>()
|
||||
var first = true
|
||||
@ -2295,7 +2460,11 @@ class Compiler(
|
||||
val dotPos = cc.savePos()
|
||||
val t = cc.next()
|
||||
if (t.type == Token.Type.DOT) {
|
||||
// continue
|
||||
val nextAfterDot = cc.peekNextNonWhitespace()
|
||||
if (nextAfterDot.type == Token.Type.LPAREN) {
|
||||
cc.restorePos(dotPos)
|
||||
break
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
cc.restorePos(dotPos)
|
||||
@ -2304,6 +2473,18 @@ class Compiler(
|
||||
}
|
||||
|
||||
val qualified = segments.joinToString(".") { it.name }
|
||||
val typeParams = currentTypeParams()
|
||||
if (segments.size == 1 && typeParams.contains(qualified)) {
|
||||
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
|
||||
true
|
||||
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
|
||||
cc.pushPendingAssign()
|
||||
true
|
||||
} else false
|
||||
val rangeEnd = cc.currentPos()
|
||||
val miniRef = MiniTypeVar(MiniRange(typeStart, rangeEnd), qualified, isNullable)
|
||||
return TypeDecl.TypeVar(qualified, isNullable) to miniRef
|
||||
}
|
||||
if (segments.size > 1) {
|
||||
lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) }
|
||||
} else {
|
||||
@ -2366,6 +2547,44 @@ class Compiler(
|
||||
return Pair(sem, miniRef)
|
||||
}
|
||||
|
||||
private fun typeDeclToTypeRef(typeDecl: TypeDecl, pos: Pos): ObjRef {
|
||||
return when (typeDecl) {
|
||||
TypeDecl.TypeAny,
|
||||
TypeDecl.TypeNullableAny,
|
||||
is TypeDecl.TypeVar -> ConstRef(Obj.rootObjectType.asReadonly)
|
||||
else -> {
|
||||
val cls = resolveTypeDeclObjClass(typeDecl)
|
||||
if (cls != null) return ConstRef(cls.asReadonly)
|
||||
val name = typeDeclName(typeDecl)
|
||||
resolveLocalTypeRef(name, pos)?.let { return it }
|
||||
throw ScriptError(pos, "unknown type $name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun typeDeclName(typeDecl: TypeDecl): String = when (typeDecl) {
|
||||
is TypeDecl.Simple -> typeDecl.name
|
||||
is TypeDecl.Generic -> typeDecl.name
|
||||
is TypeDecl.Function -> "Callable"
|
||||
is TypeDecl.TypeVar -> typeDecl.name
|
||||
TypeDecl.TypeAny -> "Object"
|
||||
TypeDecl.TypeNullableAny -> "Object?"
|
||||
}
|
||||
|
||||
private fun resolveLocalTypeRef(name: String, pos: Pos): ObjRef? {
|
||||
val slotLoc = lookupSlotLocation(name, includeModule = true) ?: return null
|
||||
captureLocalRef(name, slotLoc, pos)?.let { return it }
|
||||
return LocalSlotRef(
|
||||
name,
|
||||
slotLoc.slot,
|
||||
slotLoc.scopeId,
|
||||
slotLoc.isMutable,
|
||||
slotLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse arguments list during the call and detect last block argument
|
||||
* _following the parenthesis_ call: `(1,2) { ... }`
|
||||
@ -2502,6 +2721,11 @@ class Compiler(
|
||||
): ObjRef {
|
||||
var detectedBlockArgument = blockArgument
|
||||
val expectedReceiver = tailBlockReceiverType(left)
|
||||
val withReceiver = when (left) {
|
||||
is LocalVarRef -> if (left.name == "with") left.name else null
|
||||
is LocalSlotRef -> if (left.name == "with") left.name else null
|
||||
else -> null
|
||||
}
|
||||
val args = if (blockArgument) {
|
||||
// Leading '{' has already been consumed by the caller token branch.
|
||||
// Parse only the lambda expression as the last argument and DO NOT
|
||||
@ -2510,11 +2734,26 @@ class Compiler(
|
||||
// foo { ... }.bar() == (foo { ... }).bar()
|
||||
val callableAccessor = parseLambdaExpression(expectedReceiver)
|
||||
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
|
||||
} else {
|
||||
if (withReceiver != null) {
|
||||
val parsedArgs = parseArgsNoTailBlock().toMutableList()
|
||||
val pos = cc.savePos()
|
||||
val end = cc.next()
|
||||
if (end.type == Token.Type.LBRACE) {
|
||||
val receiverType = inferReceiverTypeFromArgs(parsedArgs)
|
||||
val callableAccessor = parseLambdaExpression(receiverType)
|
||||
parsedArgs += ParsedArgument(ExpressionStatement(callableAccessor, end.pos), end.pos)
|
||||
detectedBlockArgument = true
|
||||
} else {
|
||||
cc.restorePos(pos)
|
||||
}
|
||||
parsedArgs
|
||||
} else {
|
||||
val r = parseArgs(expectedReceiver)
|
||||
detectedBlockArgument = r.second
|
||||
r.first
|
||||
}
|
||||
}
|
||||
val implicitThisTypeName = currentImplicitThisTypeName()
|
||||
return when (left) {
|
||||
is ImplicitThisMemberRef ->
|
||||
@ -2571,6 +2810,26 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
|
||||
val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null
|
||||
val ref = stmt.ref
|
||||
val bySlot = (ref as? LocalSlotRef)?.let { slotRef ->
|
||||
slotTypeByScopeId[slotRef.scopeId]?.get(slotRef.slot)
|
||||
}
|
||||
val byName = (ref as? LocalVarRef)?.let { nameObjClass[it.name] }
|
||||
val cls = bySlot ?: byName ?: resolveInitializerObjClass(stmt)
|
||||
return cls?.className
|
||||
}
|
||||
|
||||
private fun inferReceiverTypeFromRef(ref: ObjRef): String? {
|
||||
return when (ref) {
|
||||
is LocalSlotRef -> slotTypeByScopeId[ref.scopeId]?.get(ref.slot)?.className
|
||||
is LocalVarRef -> nameObjClass[ref.name]?.className
|
||||
is QualifiedThisRef -> ref.typeName
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseAccessor(): ObjRef? {
|
||||
// could be: literal
|
||||
val t = cc.next()
|
||||
@ -3597,6 +3856,40 @@ class Compiler(
|
||||
miniSink?.onClassDecl(node)
|
||||
}
|
||||
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
|
||||
classCtx?.let { ctx ->
|
||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||
ctx.memberFieldIds.putAll(baseIds.fieldIds)
|
||||
ctx.memberMethodIds.putAll(baseIds.methodIds)
|
||||
ctx.nextFieldId = maxOf(ctx.nextFieldId, baseIds.nextFieldId)
|
||||
ctx.nextMethodId = maxOf(ctx.nextMethodId, baseIds.nextMethodId)
|
||||
for (member in ctx.declaredMembers) {
|
||||
val isOverride = ctx.memberOverrides[member] == true
|
||||
val hasBaseField = member in baseIds.fieldIds
|
||||
val hasBaseMethod = member in baseIds.methodIds
|
||||
if (isOverride) {
|
||||
if (!hasBaseField && !hasBaseMethod) {
|
||||
throw ScriptError(nameToken.pos, "member $member is marked 'override' but does not override anything")
|
||||
}
|
||||
} else {
|
||||
if (hasBaseField || hasBaseMethod) {
|
||||
throw ScriptError(nameToken.pos, "member $member overrides parent member but 'override' keyword is missing")
|
||||
}
|
||||
}
|
||||
if (!ctx.memberFieldIds.containsKey(member)) {
|
||||
ctx.memberFieldIds[member] = ctx.nextFieldId++
|
||||
}
|
||||
if (!ctx.memberMethodIds.containsKey(member)) {
|
||||
ctx.memberMethodIds[member] = ctx.nextMethodId++
|
||||
}
|
||||
}
|
||||
compileClassInfos[nameToken.value] = CompileClassInfo(
|
||||
name = nameToken.value,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
nextMethodId = ctx.nextMethodId
|
||||
)
|
||||
}
|
||||
// restore if no body starts here
|
||||
cc.restorePos(saved)
|
||||
null
|
||||
@ -4067,6 +4360,25 @@ class Compiler(
|
||||
declareLocalName(extensionWrapperName, isMutable = false)
|
||||
}
|
||||
|
||||
val typeParams = mutableSetOf<String>()
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LT) {
|
||||
cc.nextNonWhitespace()
|
||||
while (true) {
|
||||
val idTok = cc.requireToken(Token.Type.ID, "type parameter name expected")
|
||||
typeParams.add(idTok.value)
|
||||
val sep = cc.nextNonWhitespace()
|
||||
when (sep.type) {
|
||||
Token.Type.COMMA -> continue
|
||||
Token.Type.GT -> break
|
||||
Token.Type.SHR -> {
|
||||
cc.pushPendingGT()
|
||||
break
|
||||
}
|
||||
else -> sep.raiseSyntax("expected ',' or '>' in type parameter list")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val argsDeclaration: ArgsDeclaration =
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
cc.nextNonWhitespace() // consume (
|
||||
@ -4128,7 +4440,8 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
name,
|
||||
implicitThisMembers = implicitThisMembers,
|
||||
implicitThisTypeName = extTypeName
|
||||
implicitThisTypeName = extTypeName,
|
||||
typeParams = typeParams
|
||||
)
|
||||
) {
|
||||
cc.labels.add(name)
|
||||
@ -4545,6 +4858,8 @@ class Compiler(
|
||||
val rawName = when (type) {
|
||||
is TypeDecl.Simple -> type.name
|
||||
is TypeDecl.Generic -> type.name
|
||||
is TypeDecl.Function -> "Callable"
|
||||
is TypeDecl.TypeVar -> return null
|
||||
else -> return null
|
||||
}
|
||||
val name = rawName.substringAfterLast('.')
|
||||
@ -4986,6 +5301,12 @@ class Compiler(
|
||||
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||
val scopeId = slotPlan?.id
|
||||
val initObjClass = resolveInitializerObjClass(initialExpression) ?: resolveTypeDeclObjClass(varTypeDecl)
|
||||
if (initObjClass != null) {
|
||||
if (slotIndex != null && scopeId != null) {
|
||||
slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass
|
||||
}
|
||||
nameObjClass[name] = initObjClass
|
||||
}
|
||||
return VarDeclStatement(
|
||||
name,
|
||||
isMutable,
|
||||
|
||||
@ -23,7 +23,13 @@ package net.sergeych.lyng
|
||||
// very soon
|
||||
sealed class TypeDecl(val isNullable:Boolean = false) {
|
||||
// ??
|
||||
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
||||
data class Function(
|
||||
val receiver: TypeDecl?,
|
||||
val params: List<TypeDecl>,
|
||||
val returnType: TypeDecl,
|
||||
val nullable: Boolean = false
|
||||
) : TypeDecl(nullable)
|
||||
data class TypeVar(val name: String, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||
object TypeAny : TypeDecl()
|
||||
object TypeNullableAny : TypeDecl(true)
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 = {
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
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(
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -229,7 +229,7 @@ fun Iterable.sortedBy(predicate) {
|
||||
|
||||
/* Return a shuffled copy of the iterable as a list. */
|
||||
fun Iterable.shuffled() {
|
||||
val list: List = toList
|
||||
val list: List = toList()
|
||||
list.shuffle()
|
||||
list
|
||||
}
|
||||
@ -327,10 +327,10 @@ interface Delegate {
|
||||
Executes the block with `this` set to self and
|
||||
returns what the block returns.
|
||||
*/
|
||||
fun with(self, block) {
|
||||
fun with<T,R>(self: T, block: T.()->R): R {
|
||||
var result = Unset
|
||||
self.apply { result = block() }
|
||||
result
|
||||
result as R
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -4,7 +4,7 @@ better than even in C++ ;)
|
||||
|
||||
```lyng
|
||||
fun t(x) {
|
||||
// x is Object, and x is nullable
|
||||
// x is Object (non-null)
|
||||
println(x)
|
||||
}
|
||||
|
||||
@ -117,6 +117,10 @@ Notes and open questions to answer in this spec:
|
||||
|
||||
Not null by default (Object), must be specified with `?` suffix. We use Kotlin-style `!!` for non-null assertion. Therefore we check nullability at compile time, and we throw NPE only at `x!!` or `obj as X` (if obj is nullable, it is same as `obj!! as X`).
|
||||
|
||||
Return type inference and nullability:
|
||||
- If any branch or return expression is nullable, the inferred return type is nullable.
|
||||
- This is independent of whether `return` is used or implicit last-expression rules apply.
|
||||
|
||||
- Default type of untyped values: If a parameter has no type and no default, is it Object? (dynamic), or a new top type?
|
||||
|
||||
Lets discuss in more details. For example:
|
||||
@ -205,6 +209,11 @@ I think we can omit if not used. For kotlin interop: if the class has at least o
|
||||
|
||||
Compile time error unless it is an Object own method. Let's force rewriting existing code in favor of explicit casts. It will repay itself: I laready have a project on Lyng that suffers from implicit casts har to trace errors.
|
||||
|
||||
No runtime lookups or fallbacks:
|
||||
- All symbol and member resolution must be done at compile time.
|
||||
- If an extension is not known at compile time (not imported or declared before use), it is a compile-time error.
|
||||
- Runtime lookup is only possible via explicit reflection helpers.
|
||||
|
||||
Example:
|
||||
```lyng
|
||||
fun f(x) { // x: Object
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user