Compare commits
3 Commits
51b397686d
...
54c6fca0e8
| Author | SHA1 | Date | |
|---|---|---|---|
| 54c6fca0e8 | |||
| c5bf4e5039 | |||
| c9da0b256f |
@ -22,9 +22,13 @@ sealed class CodeContext {
|
||||
class Function(
|
||||
val name: String,
|
||||
val implicitThisMembers: Boolean = false,
|
||||
val implicitThisTypeName: String? = null
|
||||
val implicitThisTypeName: String? = null,
|
||||
val typeParams: Set<String> = emptySet(),
|
||||
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||
): CodeContext()
|
||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||
var typeParams: Set<String> = emptySet()
|
||||
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||
val declaredMembers = mutableSetOf<String>()
|
||||
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||
|
||||
@ -64,12 +64,38 @@ class Compiler(
|
||||
)
|
||||
private val slotPlanStack = mutableListOf<SlotPlan>()
|
||||
private var nextScopeId = 0
|
||||
private val genericFunctionDeclsStack = mutableListOf<MutableMap<String, GenericFunctionDecl>>(mutableMapOf())
|
||||
|
||||
// Track declared local variables count per function for precise capacity hints
|
||||
private val localDeclCountStack = mutableListOf<Int>()
|
||||
private val currentLocalDeclCount: Int
|
||||
get() = localDeclCountStack.lastOrNull() ?: 0
|
||||
|
||||
private data class GenericFunctionDecl(
|
||||
val typeParams: List<TypeDecl.TypeParam>,
|
||||
val params: List<ArgsDeclaration.Item>,
|
||||
val pos: Pos
|
||||
)
|
||||
|
||||
private fun pushGenericFunctionScope() {
|
||||
genericFunctionDeclsStack.add(mutableMapOf())
|
||||
}
|
||||
|
||||
private fun popGenericFunctionScope() {
|
||||
genericFunctionDeclsStack.removeLast()
|
||||
}
|
||||
|
||||
private fun currentGenericFunctionDecls(): MutableMap<String, GenericFunctionDecl> {
|
||||
return genericFunctionDeclsStack.last()
|
||||
}
|
||||
|
||||
private fun lookupGenericFunctionDecl(name: String): GenericFunctionDecl? {
|
||||
for (i in genericFunctionDeclsStack.indices.reversed()) {
|
||||
genericFunctionDeclsStack[i][name]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private inline fun <T> withLocalNames(names: Set<String>, block: () -> T): T {
|
||||
localNamesStack.add(names.toMutableSet())
|
||||
return try {
|
||||
@ -103,6 +129,8 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
|
||||
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
|
||||
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
@ -112,6 +140,36 @@ class Compiler(
|
||||
if (!record.visibility.isPublic) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
}
|
||||
for ((cls, map) in current.extensions) {
|
||||
for ((name, record) in map) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
when (record.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
extensionPropertyGetterName(cls.className, name),
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
val prop = record.value as? ObjProperty
|
||||
if (prop?.setter != null) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
extensionPropertySetterName(cls.className, name),
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> declareSlotNameIn(
|
||||
plan,
|
||||
extensionCallableName(cls.className, name),
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
|
||||
val record = current.getSlotRecord(slotIndex)
|
||||
if (!record.visibility.isPublic) continue
|
||||
@ -126,6 +184,8 @@ class Compiler(
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
val saved = cc.savePos()
|
||||
var depth = 0
|
||||
var parenDepth = 0
|
||||
var bracketDepth = 0
|
||||
fun nextNonWs(): Token {
|
||||
var t = cc.next()
|
||||
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
||||
@ -139,7 +199,12 @@ class Compiler(
|
||||
when (t.type) {
|
||||
Token.Type.LBRACE -> depth++
|
||||
Token.Type.RBRACE -> if (depth > 0) depth--
|
||||
Token.Type.LPAREN -> parenDepth++
|
||||
Token.Type.RPAREN -> if (parenDepth > 0) parenDepth--
|
||||
Token.Type.LBRACKET -> bracketDepth++
|
||||
Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth--
|
||||
Token.Type.ID -> if (depth == 0) {
|
||||
if (parenDepth > 0 || bracketDepth > 0) continue
|
||||
when (t.value) {
|
||||
"fun", "fn" -> {
|
||||
val nameToken = nextNonWs()
|
||||
@ -399,6 +464,62 @@ class Compiler(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun currentTypeParams(): Set<String> {
|
||||
val result = mutableSetOf<String>()
|
||||
pendingTypeParamStack.lastOrNull()?.let { result.addAll(it) }
|
||||
for (ctx in codeContexts.asReversed()) {
|
||||
when (ctx) {
|
||||
is CodeContext.Function -> result.addAll(ctx.typeParams)
|
||||
is CodeContext.ClassBody -> result.addAll(ctx.typeParams)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private val pendingTypeParamStack = mutableListOf<Set<String>>()
|
||||
|
||||
private fun parseTypeParamList(): List<TypeDecl.TypeParam> {
|
||||
if (cc.peekNextNonWhitespace().type != Token.Type.LT) return emptyList()
|
||||
val typeParams = mutableListOf<TypeDecl.TypeParam>()
|
||||
cc.nextNonWhitespace()
|
||||
while (true) {
|
||||
val varianceToken = cc.peekNextNonWhitespace()
|
||||
val variance = when (varianceToken.value) {
|
||||
"in" -> {
|
||||
cc.nextNonWhitespace()
|
||||
TypeDecl.Variance.In
|
||||
}
|
||||
"out" -> {
|
||||
cc.nextNonWhitespace()
|
||||
TypeDecl.Variance.Out
|
||||
}
|
||||
else -> TypeDecl.Variance.Invariant
|
||||
}
|
||||
val idTok = cc.requireToken(Token.Type.ID, "type parameter name expected")
|
||||
var bound: TypeDecl? = null
|
||||
var defaultType: TypeDecl? = null
|
||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||
bound = parseTypeExpressionWithMini().first
|
||||
}
|
||||
if (cc.skipTokenOfType(Token.Type.ASSIGN, isOptional = true)) {
|
||||
defaultType = parseTypeExpressionWithMini().first
|
||||
}
|
||||
typeParams.add(TypeDecl.TypeParam(idTok.value, variance, bound, defaultType))
|
||||
val sep = cc.nextNonWhitespace()
|
||||
when (sep.type) {
|
||||
Token.Type.COMMA -> continue
|
||||
Token.Type.GT -> break
|
||||
Token.Type.SHR -> {
|
||||
cc.pushPendingGT()
|
||||
break
|
||||
}
|
||||
else -> sep.raiseSyntax("expected ',' or '>' in type parameter list")
|
||||
}
|
||||
}
|
||||
return typeParams
|
||||
}
|
||||
|
||||
private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? {
|
||||
for (i in slotPlanStack.indices.reversed()) {
|
||||
if (!includeModule && i == 0) continue
|
||||
@ -468,6 +589,15 @@ class Compiler(
|
||||
val ids = resolveMemberIds(name, pos, null)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
|
||||
}
|
||||
val implicitThisMembers = codeContexts.any { ctx ->
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
val implicitType = if (implicitThisMembers) currentImplicitThisTypeName() else null
|
||||
if (implicitType != null && hasImplicitThisMember(name, implicitType)) {
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
|
||||
}
|
||||
val modulePlan = moduleSlotPlan()
|
||||
val moduleEntry = modulePlan?.slots?.get(name)
|
||||
if (moduleEntry != null) {
|
||||
@ -514,15 +644,6 @@ class Compiler(
|
||||
return ref
|
||||
}
|
||||
}
|
||||
val implicitThis = codeContexts.any { ctx ->
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
if (implicitThis) {
|
||||
val implicitType = currentImplicitThisTypeName()
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
|
||||
}
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
if (classContext && extensionNames.contains(name)) {
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
@ -682,6 +803,7 @@ class Compiler(
|
||||
|
||||
private suspend fun <T> inCodeContext(context: CodeContext, f: suspend () -> T): T {
|
||||
codeContexts.add(context)
|
||||
pushGenericFunctionScope()
|
||||
try {
|
||||
val res = f()
|
||||
if (context is CodeContext.ClassBody) {
|
||||
@ -692,6 +814,7 @@ class Compiler(
|
||||
}
|
||||
return res
|
||||
} finally {
|
||||
popGenericFunctionScope()
|
||||
codeContexts.removeLast()
|
||||
}
|
||||
}
|
||||
@ -915,9 +1038,16 @@ class Compiler(
|
||||
private fun resolveImplicitThisMemberIds(name: String, pos: Pos, implicitTypeName: String?): MemberIds {
|
||||
if (implicitTypeName == null) return resolveMemberIds(name, pos, null)
|
||||
val info = resolveCompileClassInfo(implicitTypeName)
|
||||
?: if (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $implicitTypeName")
|
||||
val fieldId = info.fieldIds[name]
|
||||
val methodId = info.methodIds[name]
|
||||
val fieldId = info?.fieldIds?.get(name)
|
||||
val methodId = info?.methodIds?.get(name)
|
||||
if (fieldId == null && methodId == null) {
|
||||
val cls = resolveClassByName(implicitTypeName)
|
||||
if (cls != null) {
|
||||
val fId = cls.instanceFieldIdMap()[name]
|
||||
val mId = cls.instanceMethodIdMap(includeAbstract = true)[name]
|
||||
if (fId != null || mId != null) return MemberIds(fId, mId)
|
||||
}
|
||||
}
|
||||
if (fieldId == null && methodId == null) {
|
||||
if (hasExtensionFor(implicitTypeName, name)) return MemberIds(null, null)
|
||||
if (allowUnresolvedRefs) return MemberIds(null, null)
|
||||
@ -925,6 +1055,19 @@ class Compiler(
|
||||
}
|
||||
return MemberIds(fieldId, methodId)
|
||||
}
|
||||
|
||||
private fun hasImplicitThisMember(name: String, implicitTypeName: String?): Boolean {
|
||||
if (implicitTypeName == null) return false
|
||||
val info = resolveCompileClassInfo(implicitTypeName)
|
||||
if (info != null && (info.fieldIds.containsKey(name) || info.methodIds.containsKey(name))) return true
|
||||
val cls = resolveClassByName(implicitTypeName)
|
||||
if (cls != null) {
|
||||
if (cls.instanceFieldIdMap().containsKey(name) || cls.instanceMethodIdMap(includeAbstract = true).containsKey(name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return hasExtensionFor(implicitTypeName, name)
|
||||
}
|
||||
private val currentRangeParamNames: Set<String>
|
||||
get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
|
||||
private val capturePlanStack = mutableListOf<CapturePlan>()
|
||||
@ -1443,10 +1586,19 @@ class Compiler(
|
||||
break
|
||||
}
|
||||
|
||||
val rvalue = parseExpressionLevel(level + 1)
|
||||
?: throw ScriptError(opToken.pos, "Expecting expression")
|
||||
|
||||
val res = op.generate(opToken.pos, lvalue!!, rvalue)
|
||||
val res = if (opToken.type == Token.Type.AS || opToken.type == Token.Type.ASNULL) {
|
||||
val (typeDecl, _) = parseTypeExpressionWithMini()
|
||||
val typeRef = typeDeclToTypeRef(typeDecl, opToken.pos)
|
||||
if (opToken.type == Token.Type.AS) {
|
||||
CastRef(lvalue!!, typeRef, false, opToken.pos)
|
||||
} else {
|
||||
CastRef(lvalue!!, typeRef, true, opToken.pos)
|
||||
}
|
||||
} else {
|
||||
val rvalue = parseExpressionLevel(level + 1)
|
||||
?: throw ScriptError(opToken.pos, "Expecting expression")
|
||||
op.generate(opToken.pos, lvalue!!, rvalue)
|
||||
}
|
||||
if (opToken.type == Token.Type.ASSIGN) {
|
||||
val ctx = codeContexts.lastOrNull()
|
||||
if (ctx is CodeContext.ClassBody) {
|
||||
@ -1527,7 +1679,10 @@ class Compiler(
|
||||
Token.Type.LPAREN -> {
|
||||
cc.next()
|
||||
// instance method call
|
||||
val parsed = parseArgs()
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val parsed = parseArgs(receiverType)
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
if (left is LocalVarRef && left.name == "scope") {
|
||||
@ -1578,7 +1733,10 @@ class Compiler(
|
||||
// single lambda arg, like assertThrows { ... }
|
||||
cc.next()
|
||||
isCall = true
|
||||
val lambda = parseLambdaExpression()
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val lambda = parseLambdaExpression(receiverType)
|
||||
val argPos = next.pos
|
||||
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
|
||||
operand = when (left) {
|
||||
@ -2274,6 +2432,119 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun parseTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
return parseTypeUnionWithMini()
|
||||
}
|
||||
|
||||
private fun parseTypeUnionWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
var left = parseTypeIntersectionWithMini()
|
||||
val options = mutableListOf(left)
|
||||
while (cc.skipTokenOfType(Token.Type.BITOR, isOptional = true)) {
|
||||
options += parseTypeIntersectionWithMini()
|
||||
}
|
||||
if (options.size == 1) return left
|
||||
val rangeStart = options.first().second.range.start
|
||||
val rangeEnd = cc.currentPos()
|
||||
val mini = MiniTypeUnion(MiniRange(rangeStart, rangeEnd), options.map { it.second }, nullable = false)
|
||||
return TypeDecl.Union(options.map { it.first }, nullable = false) to mini
|
||||
}
|
||||
|
||||
private fun parseTypeIntersectionWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
var left = parseTypePrimaryWithMini()
|
||||
val options = mutableListOf(left)
|
||||
while (cc.skipTokenOfType(Token.Type.BITAND, isOptional = true)) {
|
||||
options += parseTypePrimaryWithMini()
|
||||
}
|
||||
if (options.size == 1) return left
|
||||
val rangeStart = options.first().second.range.start
|
||||
val rangeEnd = cc.currentPos()
|
||||
val mini = MiniTypeIntersection(MiniRange(rangeStart, rangeEnd), options.map { it.second }, nullable = false)
|
||||
return TypeDecl.Intersection(options.map { it.first }, nullable = false) to mini
|
||||
}
|
||||
|
||||
private fun parseTypePrimaryWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
parseFunctionTypeWithMini()?.let { return it }
|
||||
return parseSimpleTypeExpressionWithMini()
|
||||
}
|
||||
|
||||
private fun parseFunctionTypeWithMini(): Pair<TypeDecl, MiniTypeRef>? {
|
||||
val saved = cc.savePos()
|
||||
val startPos = cc.currentPos()
|
||||
|
||||
fun parseParamTypes(): List<Pair<TypeDecl, MiniTypeRef>> {
|
||||
val params = mutableListOf<Pair<TypeDecl, MiniTypeRef>>()
|
||||
cc.skipWsTokens()
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.RPAREN) {
|
||||
cc.nextNonWhitespace()
|
||||
return params
|
||||
}
|
||||
while (true) {
|
||||
val (paramDecl, paramMini) = parseTypeExpressionWithMini()
|
||||
params += paramDecl to paramMini
|
||||
val sep = cc.nextNonWhitespace()
|
||||
when (sep.type) {
|
||||
Token.Type.COMMA -> continue
|
||||
Token.Type.RPAREN -> return params
|
||||
else -> sep.raiseSyntax("expected ',' or ')' in function type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var receiverDecl: TypeDecl? = null
|
||||
var receiverMini: MiniTypeRef? = null
|
||||
|
||||
val first = cc.peekNextNonWhitespace()
|
||||
if (first.type == Token.Type.LPAREN) {
|
||||
cc.nextNonWhitespace()
|
||||
} else {
|
||||
val recv = parseSimpleTypeExpressionWithMini()
|
||||
val dotPos = cc.savePos()
|
||||
if (cc.skipTokenOfType(Token.Type.DOT, isOptional = true) && cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
receiverDecl = recv.first
|
||||
receiverMini = recv.second
|
||||
cc.nextNonWhitespace()
|
||||
} else {
|
||||
cc.restorePos(saved)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val params = parseParamTypes()
|
||||
val arrow = cc.nextNonWhitespace()
|
||||
if (arrow.type != Token.Type.ARROW) {
|
||||
cc.restorePos(saved)
|
||||
return null
|
||||
}
|
||||
val (retDecl, retMini) = parseTypeExpressionWithMini()
|
||||
|
||||
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
|
||||
true
|
||||
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
|
||||
cc.pushPendingAssign()
|
||||
true
|
||||
} else false
|
||||
|
||||
val rangeStart = when (receiverMini) {
|
||||
null -> startPos
|
||||
else -> receiverMini.range.start
|
||||
}
|
||||
val rangeEnd = cc.currentPos()
|
||||
val mini = MiniFunctionType(
|
||||
range = MiniRange(rangeStart, rangeEnd),
|
||||
receiver = receiverMini,
|
||||
params = params.map { it.second },
|
||||
returnType = retMini,
|
||||
nullable = isNullable
|
||||
)
|
||||
val sem = TypeDecl.Function(
|
||||
receiver = receiverDecl,
|
||||
params = params.map { it.first },
|
||||
returnType = retDecl,
|
||||
nullable = isNullable
|
||||
)
|
||||
return sem to mini
|
||||
}
|
||||
|
||||
private fun parseSimpleTypeExpressionWithMini(): Pair<TypeDecl, MiniTypeRef> {
|
||||
// Parse a qualified base name: ID ('.' ID)*
|
||||
val segments = mutableListOf<MiniTypeName.Segment>()
|
||||
var first = true
|
||||
@ -2295,7 +2566,11 @@ class Compiler(
|
||||
val dotPos = cc.savePos()
|
||||
val t = cc.next()
|
||||
if (t.type == Token.Type.DOT) {
|
||||
// continue
|
||||
val nextAfterDot = cc.peekNextNonWhitespace()
|
||||
if (nextAfterDot.type == Token.Type.LPAREN) {
|
||||
cc.restorePos(dotPos)
|
||||
break
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
cc.restorePos(dotPos)
|
||||
@ -2304,6 +2579,18 @@ class Compiler(
|
||||
}
|
||||
|
||||
val qualified = segments.joinToString(".") { it.name }
|
||||
val typeParams = currentTypeParams()
|
||||
if (segments.size == 1 && typeParams.contains(qualified)) {
|
||||
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
|
||||
true
|
||||
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
|
||||
cc.pushPendingAssign()
|
||||
true
|
||||
} else false
|
||||
val rangeEnd = cc.currentPos()
|
||||
val miniRef = MiniTypeVar(MiniRange(typeStart, rangeEnd), qualified, isNullable)
|
||||
return TypeDecl.TypeVar(qualified, isNullable) to miniRef
|
||||
}
|
||||
if (segments.size > 1) {
|
||||
lastPos?.let { pos -> resolutionSink?.reference(qualified, pos) }
|
||||
} else {
|
||||
@ -2366,6 +2653,126 @@ class Compiler(
|
||||
return Pair(sem, miniRef)
|
||||
}
|
||||
|
||||
private fun typeDeclToTypeRef(typeDecl: TypeDecl, pos: Pos): ObjRef {
|
||||
return when (typeDecl) {
|
||||
TypeDecl.TypeAny,
|
||||
TypeDecl.TypeNullableAny -> ConstRef(Obj.rootObjectType.asReadonly)
|
||||
is TypeDecl.TypeVar -> resolveLocalTypeRef(typeDecl.name, pos) ?: ConstRef(Obj.rootObjectType.asReadonly)
|
||||
else -> {
|
||||
val cls = resolveTypeDeclObjClass(typeDecl)
|
||||
if (cls != null) return ConstRef(cls.asReadonly)
|
||||
val name = typeDeclName(typeDecl)
|
||||
resolveLocalTypeRef(name, pos)?.let { return it }
|
||||
throw ScriptError(pos, "unknown type $name")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun typeDeclName(typeDecl: TypeDecl): String = when (typeDecl) {
|
||||
is TypeDecl.Simple -> typeDecl.name
|
||||
is TypeDecl.Generic -> typeDecl.name
|
||||
is TypeDecl.Function -> "Callable"
|
||||
is TypeDecl.TypeVar -> typeDecl.name
|
||||
is TypeDecl.Union -> typeDecl.options.joinToString(" | ") { typeDeclName(it) }
|
||||
is TypeDecl.Intersection -> typeDecl.options.joinToString(" & ") { typeDeclName(it) }
|
||||
TypeDecl.TypeAny -> "Object"
|
||||
TypeDecl.TypeNullableAny -> "Object?"
|
||||
}
|
||||
|
||||
private fun inferObjClassFromRef(ref: ObjRef): ObjClass? = when (ref) {
|
||||
is ConstRef -> ref.constValue as? ObjClass ?: (ref.constValue as? Obj)?.objClass
|
||||
is LocalVarRef -> nameObjClass[ref.name]
|
||||
is LocalSlotRef -> nameObjClass[ref.name]
|
||||
is ListLiteralRef -> ObjList.type
|
||||
is MapLiteralRef -> ObjMap.type
|
||||
is RangeRef -> ObjRange.type
|
||||
is CastRef -> resolveTypeRefClass(ref.castTypeRef())
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun resolveTypeRefClass(ref: ObjRef): ObjClass? = when (ref) {
|
||||
is ConstRef -> ref.constValue as? ObjClass
|
||||
is LocalSlotRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
|
||||
is LocalVarRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun typeParamBoundSatisfied(argClass: ObjClass, bound: TypeDecl): Boolean = when (bound) {
|
||||
is TypeDecl.Union -> bound.options.any { typeParamBoundSatisfied(argClass, it) }
|
||||
is TypeDecl.Intersection -> bound.options.all { typeParamBoundSatisfied(argClass, it) }
|
||||
is TypeDecl.Simple, is TypeDecl.Generic -> {
|
||||
val boundClass = resolveTypeDeclObjClass(bound) ?: return false
|
||||
argClass == boundClass || argClass.allParentsSet.contains(boundClass)
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
|
||||
private fun checkGenericBoundsAtCall(
|
||||
name: String,
|
||||
args: List<ParsedArgument>,
|
||||
pos: Pos
|
||||
) {
|
||||
val decl = lookupGenericFunctionDecl(name) ?: return
|
||||
val inferred = mutableMapOf<String, ObjClass>()
|
||||
val limit = minOf(args.size, decl.params.size)
|
||||
for (i in 0 until limit) {
|
||||
val paramType = decl.params[i].type
|
||||
val argRef = (args[i].value as? ExpressionStatement)?.ref ?: continue
|
||||
val argClass = inferObjClassFromRef(argRef) ?: continue
|
||||
if (paramType is TypeDecl.TypeVar) {
|
||||
inferred[paramType.name] = argClass
|
||||
}
|
||||
}
|
||||
for (tp in decl.typeParams) {
|
||||
val argClass = inferred[tp.name] ?: continue
|
||||
val bound = tp.bound ?: continue
|
||||
if (!typeParamBoundSatisfied(argClass, bound)) {
|
||||
throw ScriptError(pos, "type argument ${argClass.className} does not satisfy bound ${typeDeclName(bound)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindTypeParamsAtRuntime(
|
||||
context: Scope,
|
||||
argsDeclaration: ArgsDeclaration,
|
||||
typeParams: List<TypeDecl.TypeParam>
|
||||
) {
|
||||
if (typeParams.isEmpty()) return
|
||||
val inferred = mutableMapOf<String, ObjClass>()
|
||||
for (param in argsDeclaration.params) {
|
||||
val paramType = param.type
|
||||
if (paramType is TypeDecl.TypeVar) {
|
||||
val rec = context.getLocalRecordDirect(param.name) ?: continue
|
||||
val value = rec.value
|
||||
if (value is Obj) inferred[paramType.name] = value.objClass
|
||||
}
|
||||
}
|
||||
for (tp in typeParams) {
|
||||
val cls = inferred[tp.name]
|
||||
?: tp.defaultType?.let { resolveTypeDeclObjClass(it) }
|
||||
?: Obj.rootObjectType
|
||||
context.addConst(tp.name, cls)
|
||||
val bound = tp.bound ?: continue
|
||||
if (!typeParamBoundSatisfied(cls, bound)) {
|
||||
context.raiseError("type argument ${cls.className} does not satisfy bound ${typeDeclName(bound)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveLocalTypeRef(name: String, pos: Pos): ObjRef? {
|
||||
val slotLoc = lookupSlotLocation(name, includeModule = true) ?: return null
|
||||
captureLocalRef(name, slotLoc, pos)?.let { return it }
|
||||
return LocalSlotRef(
|
||||
name,
|
||||
slotLoc.slot,
|
||||
slotLoc.scopeId,
|
||||
slotLoc.isMutable,
|
||||
slotLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse arguments list during the call and detect last block argument
|
||||
* _following the parenthesis_ call: `(1,2) { ... }`
|
||||
@ -2502,6 +2909,11 @@ class Compiler(
|
||||
): ObjRef {
|
||||
var detectedBlockArgument = blockArgument
|
||||
val expectedReceiver = tailBlockReceiverType(left)
|
||||
val withReceiver = when (left) {
|
||||
is LocalVarRef -> if (left.name == "with") left.name else null
|
||||
is LocalSlotRef -> if (left.name == "with") left.name else null
|
||||
else -> null
|
||||
}
|
||||
val args = if (blockArgument) {
|
||||
// Leading '{' has already been consumed by the caller token branch.
|
||||
// Parse only the lambda expression as the last argument and DO NOT
|
||||
@ -2511,9 +2923,24 @@ class Compiler(
|
||||
val callableAccessor = parseLambdaExpression(expectedReceiver)
|
||||
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
|
||||
} else {
|
||||
val r = parseArgs(expectedReceiver)
|
||||
detectedBlockArgument = r.second
|
||||
r.first
|
||||
if (withReceiver != null) {
|
||||
val parsedArgs = parseArgsNoTailBlock().toMutableList()
|
||||
val pos = cc.savePos()
|
||||
val end = cc.next()
|
||||
if (end.type == Token.Type.LBRACE) {
|
||||
val receiverType = inferReceiverTypeFromArgs(parsedArgs)
|
||||
val callableAccessor = parseLambdaExpression(receiverType)
|
||||
parsedArgs += ParsedArgument(ExpressionStatement(callableAccessor, end.pos), end.pos)
|
||||
detectedBlockArgument = true
|
||||
} else {
|
||||
cc.restorePos(pos)
|
||||
}
|
||||
parsedArgs
|
||||
} else {
|
||||
val r = parseArgs(expectedReceiver)
|
||||
detectedBlockArgument = r.second
|
||||
r.first
|
||||
}
|
||||
}
|
||||
val implicitThisTypeName = currentImplicitThisTypeName()
|
||||
return when (left) {
|
||||
@ -2544,6 +2971,7 @@ class Compiler(
|
||||
implicitThisTypeName
|
||||
)
|
||||
} else {
|
||||
checkGenericBoundsAtCall(left.name, args, left.pos())
|
||||
CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
}
|
||||
@ -2564,6 +2992,7 @@ class Compiler(
|
||||
implicitThisTypeName
|
||||
)
|
||||
} else {
|
||||
checkGenericBoundsAtCall(left.name, args, left.pos())
|
||||
CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
}
|
||||
@ -2571,6 +3000,26 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
|
||||
val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null
|
||||
val ref = stmt.ref
|
||||
val bySlot = (ref as? LocalSlotRef)?.let { slotRef ->
|
||||
slotTypeByScopeId[slotRef.scopeId]?.get(slotRef.slot)
|
||||
}
|
||||
val byName = (ref as? LocalVarRef)?.let { nameObjClass[it.name] }
|
||||
val cls = bySlot ?: byName ?: resolveInitializerObjClass(stmt)
|
||||
return cls?.className
|
||||
}
|
||||
|
||||
private fun inferReceiverTypeFromRef(ref: ObjRef): String? {
|
||||
return when (ref) {
|
||||
is LocalSlotRef -> slotTypeByScopeId[ref.scopeId]?.get(ref.slot)?.className
|
||||
is LocalVarRef -> nameObjClass[ref.name]?.className
|
||||
is QualifiedThisRef -> ref.typeName
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseAccessor(): ObjRef? {
|
||||
// could be: literal
|
||||
val t = cc.next()
|
||||
@ -3443,10 +3892,20 @@ class Compiler(
|
||||
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
||||
return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) {
|
||||
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
|
||||
val constructorArgsDeclaration =
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||
parseArgsDeclaration(isClassDeclaration = true)
|
||||
else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
val typeParamDecls = parseTypeParamList()
|
||||
classCtx?.typeParamDecls = typeParamDecls
|
||||
val classTypeParams = typeParamDecls.map { it.name }.toSet()
|
||||
classCtx?.typeParams = classTypeParams
|
||||
pendingTypeParamStack.add(classTypeParams)
|
||||
val constructorArgsDeclaration: ArgsDeclaration?
|
||||
try {
|
||||
constructorArgsDeclaration =
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||
parseArgsDeclaration(isClassDeclaration = true)
|
||||
else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
} finally {
|
||||
pendingTypeParamStack.removeLast()
|
||||
}
|
||||
|
||||
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||
throw ScriptError(
|
||||
@ -3470,18 +3929,27 @@ class Compiler(
|
||||
data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
|
||||
|
||||
val baseSpecs = mutableListOf<BaseSpec>()
|
||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||
do {
|
||||
val baseId = cc.requireToken(Token.Type.ID, "base class name expected")
|
||||
resolutionSink?.reference(baseId.value, baseId.pos)
|
||||
var argsList: List<ParsedArgument>? = null
|
||||
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||
// Parse args without consuming any following block so that a class body can follow safely
|
||||
argsList = parseArgsNoTailBlock()
|
||||
}
|
||||
baseSpecs += BaseSpec(baseId.value, argsList)
|
||||
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
|
||||
pendingTypeParamStack.add(classTypeParams)
|
||||
try {
|
||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||
do {
|
||||
val (baseDecl, _) = parseSimpleTypeExpressionWithMini()
|
||||
val baseName = when (baseDecl) {
|
||||
is TypeDecl.Simple -> baseDecl.name
|
||||
is TypeDecl.Generic -> baseDecl.name
|
||||
else -> throw ScriptError(cc.currentPos(), "base class name expected")
|
||||
}
|
||||
var argsList: List<ParsedArgument>? = null
|
||||
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||
// Parse args without consuming any following block so that a class body can follow safely
|
||||
argsList = parseArgsNoTailBlock()
|
||||
}
|
||||
baseSpecs += BaseSpec(baseName, argsList)
|
||||
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
|
||||
}
|
||||
} finally {
|
||||
pendingTypeParamStack.removeLast()
|
||||
}
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
@ -3597,6 +4065,40 @@ class Compiler(
|
||||
miniSink?.onClassDecl(node)
|
||||
}
|
||||
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
|
||||
classCtx?.let { ctx ->
|
||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||
ctx.memberFieldIds.putAll(baseIds.fieldIds)
|
||||
ctx.memberMethodIds.putAll(baseIds.methodIds)
|
||||
ctx.nextFieldId = maxOf(ctx.nextFieldId, baseIds.nextFieldId)
|
||||
ctx.nextMethodId = maxOf(ctx.nextMethodId, baseIds.nextMethodId)
|
||||
for (member in ctx.declaredMembers) {
|
||||
val isOverride = ctx.memberOverrides[member] == true
|
||||
val hasBaseField = member in baseIds.fieldIds
|
||||
val hasBaseMethod = member in baseIds.methodIds
|
||||
if (isOverride) {
|
||||
if (!hasBaseField && !hasBaseMethod) {
|
||||
throw ScriptError(nameToken.pos, "member $member is marked 'override' but does not override anything")
|
||||
}
|
||||
} else {
|
||||
if (hasBaseField || hasBaseMethod) {
|
||||
throw ScriptError(nameToken.pos, "member $member overrides parent member but 'override' keyword is missing")
|
||||
}
|
||||
}
|
||||
if (!ctx.memberFieldIds.containsKey(member)) {
|
||||
ctx.memberFieldIds[member] = ctx.nextFieldId++
|
||||
}
|
||||
if (!ctx.memberMethodIds.containsKey(member)) {
|
||||
ctx.memberMethodIds[member] = ctx.nextMethodId++
|
||||
}
|
||||
}
|
||||
compileClassInfos[nameToken.value] = CompileClassInfo(
|
||||
name = nameToken.value,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
nextMethodId = ctx.nextMethodId
|
||||
)
|
||||
}
|
||||
// restore if no body starts here
|
||||
cc.restorePos(saved)
|
||||
null
|
||||
@ -4067,16 +4569,29 @@ class Compiler(
|
||||
declareLocalName(extensionWrapperName, isMutable = false)
|
||||
}
|
||||
|
||||
val argsDeclaration: ArgsDeclaration =
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
cc.nextNonWhitespace() // consume (
|
||||
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
} else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
val typeParamDecls = parseTypeParamList()
|
||||
val typeParams = typeParamDecls.map { it.name }.toSet()
|
||||
pendingTypeParamStack.add(typeParams)
|
||||
val argsDeclaration: ArgsDeclaration
|
||||
val returnTypeMini: MiniTypeRef?
|
||||
try {
|
||||
argsDeclaration =
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
cc.nextNonWhitespace() // consume (
|
||||
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
} else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
|
||||
// Optional return type
|
||||
val returnTypeMini: MiniTypeRef? = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
|
||||
parseTypeDeclarationWithMini().second
|
||||
} else null
|
||||
if (typeParamDecls.isNotEmpty() && declKind != SymbolKind.MEMBER) {
|
||||
currentGenericFunctionDecls()[name] = GenericFunctionDecl(typeParamDecls, argsDeclaration.params, nameStartPos)
|
||||
}
|
||||
|
||||
// Optional return type
|
||||
returnTypeMini = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
|
||||
parseTypeDeclarationWithMini().second
|
||||
} else null
|
||||
} finally {
|
||||
pendingTypeParamStack.removeLast()
|
||||
}
|
||||
|
||||
var isDelegated = false
|
||||
var delegateExpression: Statement? = null
|
||||
@ -4128,15 +4643,18 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
name,
|
||||
implicitThisMembers = implicitThisMembers,
|
||||
implicitThisTypeName = extTypeName
|
||||
implicitThisTypeName = extTypeName,
|
||||
typeParams = typeParams,
|
||||
typeParamDecls = typeParamDecls
|
||||
)
|
||||
) {
|
||||
cc.labels.add(name)
|
||||
outerLabel?.let { cc.labels.add(it) }
|
||||
|
||||
val paramNamesList = argsDeclaration.params.map { it.name }
|
||||
val typeParamNames = typeParamDecls.map { it.name }
|
||||
val paramNames: Set<String> = paramNamesList.toSet()
|
||||
val paramSlotPlan = buildParamSlotPlan(paramNamesList)
|
||||
val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames)
|
||||
val capturePlan = CapturePlan(paramSlotPlan)
|
||||
val rangeParamNames = argsDeclaration.params
|
||||
.filter { isRangeType(it.type) }
|
||||
@ -4238,6 +4756,7 @@ class Compiler(
|
||||
|
||||
// load params from caller context
|
||||
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
|
||||
bindTypeParamsAtRuntime(context, argsDeclaration, typeParamDecls)
|
||||
if (extTypeName != null) {
|
||||
context.thisObj = callerContext.thisObj
|
||||
}
|
||||
@ -4545,6 +5064,10 @@ class Compiler(
|
||||
val rawName = when (type) {
|
||||
is TypeDecl.Simple -> type.name
|
||||
is TypeDecl.Generic -> type.name
|
||||
is TypeDecl.Function -> "Callable"
|
||||
is TypeDecl.TypeVar -> return null
|
||||
is TypeDecl.Union -> return null
|
||||
is TypeDecl.Intersection -> return null
|
||||
else -> return null
|
||||
}
|
||||
val name = rawName.substringAfterLast('.')
|
||||
@ -4986,6 +5509,12 @@ class Compiler(
|
||||
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||
val scopeId = slotPlan?.id
|
||||
val initObjClass = resolveInitializerObjClass(initialExpression) ?: resolveTypeDeclObjClass(varTypeDecl)
|
||||
if (initObjClass != null) {
|
||||
if (slotIndex != null && scopeId != null) {
|
||||
slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass
|
||||
}
|
||||
nameObjClass[name] = initObjClass
|
||||
}
|
||||
return VarDeclStatement(
|
||||
name,
|
||||
isMutable,
|
||||
|
||||
@ -22,8 +22,23 @@ package net.sergeych.lyng
|
||||
// this is highly experimental and subject to complete redesign
|
||||
// very soon
|
||||
sealed class TypeDecl(val isNullable:Boolean = false) {
|
||||
enum class Variance { In, Out, Invariant }
|
||||
// ??
|
||||
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
||||
data class Function(
|
||||
val receiver: TypeDecl?,
|
||||
val params: List<TypeDecl>,
|
||||
val returnType: TypeDecl,
|
||||
val nullable: Boolean = false
|
||||
) : TypeDecl(nullable)
|
||||
data class TypeVar(val name: String, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||
data class Union(val options: List<TypeDecl>, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||
data class Intersection(val options: List<TypeDecl>, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||
data class TypeParam(
|
||||
val name: String,
|
||||
val variance: Variance = Variance.Invariant,
|
||||
val bound: TypeDecl? = null,
|
||||
val defaultType: TypeDecl? = null
|
||||
)
|
||||
object TypeAny : TypeDecl()
|
||||
object TypeNullableAny : TypeDecl(true)
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1025,6 +1025,8 @@ object DocLookupUtils {
|
||||
is MiniGenericType -> simpleClassNameOf(t.base)
|
||||
is MiniFunctionType -> null
|
||||
is MiniTypeVar -> null
|
||||
is MiniTypeUnion -> null
|
||||
is MiniTypeIntersection -> null
|
||||
}
|
||||
|
||||
fun typeOf(t: MiniTypeRef?): String = when (t) {
|
||||
@ -1035,6 +1037,8 @@ object DocLookupUtils {
|
||||
r + "(" + t.params.joinToString(", ") { typeOf(it) } + ") -> " + typeOf(t.returnType) + (if (t.nullable) "?" else "")
|
||||
}
|
||||
is MiniTypeVar -> t.name + (if (t.nullable) "?" else "")
|
||||
is MiniTypeUnion -> t.options.joinToString(" | ") { typeOf(it) } + (if (t.nullable) "?" else "")
|
||||
is MiniTypeIntersection -> t.options.joinToString(" & ") { typeOf(it) } + (if (t.nullable) "?" else "")
|
||||
null -> ""
|
||||
}
|
||||
|
||||
|
||||
@ -150,6 +150,18 @@ data class MiniTypeVar(
|
||||
val nullable: Boolean
|
||||
) : MiniTypeRef
|
||||
|
||||
data class MiniTypeUnion(
|
||||
override val range: MiniRange,
|
||||
val options: List<MiniTypeRef>,
|
||||
val nullable: Boolean
|
||||
) : MiniTypeRef
|
||||
|
||||
data class MiniTypeIntersection(
|
||||
override val range: MiniRange,
|
||||
val options: List<MiniTypeRef>,
|
||||
val nullable: Boolean
|
||||
) : MiniTypeRef
|
||||
|
||||
// Script and declarations (lean subset; can be extended later)
|
||||
sealed interface MiniNamedDecl : MiniNode {
|
||||
val name: String
|
||||
|
||||
@ -36,20 +36,19 @@ val ObjIterable by lazy {
|
||||
code = null
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
addFnDoc(
|
||||
name = "toList",
|
||||
doc = "Collect elements of this iterable into a new list.",
|
||||
type = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
}
|
||||
ObjList(result)
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
}
|
||||
)
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
// it is not effective, but it is open:
|
||||
addFnDoc(
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5273,6 +5273,40 @@ class ScriptTest {
|
||||
assertEquals(ObjFalse, scope.eval("isInt(\"42\")"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGenericBoundsAndReifiedTypeParams() = runTest {
|
||||
val resInt = eval(
|
||||
"""
|
||||
fun square<T: Int | Real>(x: T) = x * x
|
||||
square(2)
|
||||
""".trimIndent()
|
||||
)
|
||||
assertEquals(4L, (resInt as ObjInt).value)
|
||||
val resReal = eval(
|
||||
"""
|
||||
fun square<T: Int | Real>(x: T) = x * x
|
||||
square(1.5)
|
||||
""".trimIndent()
|
||||
)
|
||||
assertEquals(2.25, (resReal as ObjReal).value, 0.00001)
|
||||
assertFailsWith<ScriptError> {
|
||||
eval(
|
||||
"""
|
||||
fun square<T: Int | Real>(x: T) = x * x
|
||||
square("x")
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
val reified = eval(
|
||||
"""
|
||||
fun sameType<T>(x: T, y: Object) = y is T
|
||||
sameType(1, "a")
|
||||
""".trimIndent()
|
||||
)
|
||||
assertEquals(false, (reified as ObjBool).value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilterBug() = runTest {
|
||||
eval(
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package lyng.stdlib
|
||||
|
||||
// desired type: FlowBuilder.()->void (function types not yet supported in type grammar)
|
||||
extern fun flow(builder)
|
||||
extern fun flow(builder: FlowBuilder.()->Void): Flow
|
||||
|
||||
/* Built-in exception type. */
|
||||
extern class Exception
|
||||
@ -10,10 +9,10 @@ extern class NotImplementedException
|
||||
extern class Delegate
|
||||
|
||||
// Built-in math helpers (implemented in host runtime).
|
||||
extern fun abs(x)
|
||||
extern fun ln(x)
|
||||
extern fun pow(x, y)
|
||||
extern fun sqrt(x)
|
||||
extern fun abs(x: Object): Real
|
||||
extern fun ln(x: Object): Real
|
||||
extern fun pow(x: Object, y: Object): Real
|
||||
extern fun sqrt(x: Object): Real
|
||||
|
||||
// Last regex match result, updated by =~ / !~.
|
||||
var $~ = null
|
||||
@ -22,7 +21,7 @@ var $~ = null
|
||||
Wrap a builder into a zero-argument thunk that computes once and caches the result.
|
||||
The first call invokes builder() and stores the value; subsequent calls return the cached value.
|
||||
*/
|
||||
fun cached(builder) {
|
||||
fun cached<T>(builder: ()->T): ()->T {
|
||||
var calculated = false
|
||||
var value = null
|
||||
{
|
||||
@ -30,13 +29,13 @@ fun cached(builder) {
|
||||
value = builder()
|
||||
calculated = true
|
||||
}
|
||||
value
|
||||
value as T
|
||||
}
|
||||
}
|
||||
/* Filter elements of this iterable using the provided predicate and provide a flow
|
||||
of results. Coudl be used to map infinte flows, etc.
|
||||
*/
|
||||
fun Iterable.filterFlow(predicate): Flow {
|
||||
fun Iterable.filterFlow<T>(predicate: (T)->Bool): Flow<T> {
|
||||
val list = this
|
||||
flow {
|
||||
for( item in list ) {
|
||||
@ -50,8 +49,8 @@ fun Iterable.filterFlow(predicate): Flow {
|
||||
/*
|
||||
Filter this iterable and return List of elements
|
||||
*/
|
||||
fun Iterable.filter(predicate) {
|
||||
var result: List = List()
|
||||
fun Iterable.filter<T>(predicate: (T)->Bool): List<T> {
|
||||
var result: List<T> = List()
|
||||
for( item in this ) if( predicate(item) ) result += item
|
||||
result
|
||||
}
|
||||
@ -59,7 +58,7 @@ fun Iterable.filter(predicate) {
|
||||
/*
|
||||
Count all items in this iterable for which predicate returns true
|
||||
*/
|
||||
fun Iterable.count(predicate): Int {
|
||||
fun Iterable.count<T>(predicate: (T)->Bool): Int {
|
||||
var hits = 0
|
||||
this.forEach {
|
||||
if( predicate(it) ) hits++
|
||||
@ -70,24 +69,24 @@ fun Iterable.count(predicate): Int {
|
||||
filter out all null elements from this collection (Iterable); flow of
|
||||
non-null elements is returned
|
||||
*/
|
||||
fun Iterable.filterFlowNotNull(): Flow {
|
||||
fun Iterable.filterFlowNotNull<T>(): Flow<T> {
|
||||
filterFlow { it != null }
|
||||
}
|
||||
|
||||
/* Filter non-null elements and collect them into a List
|
||||
*/
|
||||
fun Iterable.filterNotNull(): List {
|
||||
fun Iterable.filterNotNull<T>(): List<T> {
|
||||
filter { it != null }
|
||||
}
|
||||
|
||||
/* Skip the first N elements of this iterable. */
|
||||
fun Iterable.drop(n) {
|
||||
fun Iterable.drop<T>(n: Int): List<T> {
|
||||
var cnt = 0
|
||||
filter { cnt++ >= n }
|
||||
}
|
||||
|
||||
/* Return the first element or throw if the iterable is empty. */
|
||||
val Iterable.first get() {
|
||||
val Iterable.first: Object get() {
|
||||
val i: Iterator = iterator()
|
||||
if( !i.hasNext() ) throw NoSuchElementException()
|
||||
i.next().also { i.cancelIteration() }
|
||||
@ -97,7 +96,7 @@ val Iterable.first get() {
|
||||
Return the first element that matches the predicate or throws
|
||||
NuSuchElementException
|
||||
*/
|
||||
fun Iterable.findFirst(predicate) {
|
||||
fun Iterable.findFirst<T>(predicate: (T)->Bool): T {
|
||||
for( x in this ) {
|
||||
if( predicate(x) )
|
||||
break x
|
||||
@ -108,7 +107,7 @@ fun Iterable.findFirst(predicate) {
|
||||
/*
|
||||
return the first element matching the predicate or null
|
||||
*/
|
||||
fun Iterable.findFirstOrNull(predicate) {
|
||||
fun Iterable.findFirstOrNull<T>(predicate: (T)->Bool): T? {
|
||||
for( x in this ) {
|
||||
if( predicate(x) )
|
||||
break x
|
||||
@ -118,7 +117,7 @@ fun Iterable.findFirstOrNull(predicate) {
|
||||
|
||||
|
||||
/* Return the last element or throw if the iterable is empty. */
|
||||
val Iterable.last get() {
|
||||
val Iterable.last: Object get() {
|
||||
var found = false
|
||||
var element = null
|
||||
for( i in this ) {
|
||||
@ -130,7 +129,7 @@ val Iterable.last get() {
|
||||
}
|
||||
|
||||
/* Emit all but the last N elements of this iterable. */
|
||||
fun Iterable.dropLast(n) {
|
||||
fun Iterable.dropLast<T>(n: Int): Flow<T> {
|
||||
val list = this
|
||||
val buffer = RingBuffer(n)
|
||||
flow {
|
||||
@ -143,17 +142,17 @@ fun Iterable.dropLast(n) {
|
||||
}
|
||||
|
||||
/* Return the last N elements of this iterable as a buffer/list. */
|
||||
fun Iterable.takeLast(n) {
|
||||
val buffer = RingBuffer(n)
|
||||
fun Iterable.takeLast<T>(n: Int): RingBuffer<T> {
|
||||
val buffer: RingBuffer<T> = RingBuffer(n)
|
||||
for( item in this ) buffer += item
|
||||
buffer
|
||||
}
|
||||
|
||||
/* Join elements into a string with a separator (separator parameter) and optional transformer. */
|
||||
fun Iterable.joinToString(separator=" ", transformer=null) {
|
||||
fun Iterable.joinToString<T>(separator: String=" ", transformer: (T)->Object = { it }): String {
|
||||
var result = null
|
||||
for( part in this ) {
|
||||
val transformed = transformer?(part)?.toString() ?: part.toString()
|
||||
val transformed = transformer(part).toString()
|
||||
if( result == null ) result = transformed
|
||||
else result += separator + transformed
|
||||
}
|
||||
@ -161,7 +160,7 @@ fun Iterable.joinToString(separator=" ", transformer=null) {
|
||||
}
|
||||
|
||||
/* Return true if any element matches the predicate. */
|
||||
fun Iterable.any(predicate): Bool {
|
||||
fun Iterable.any<T>(predicate: (T)->Bool): Bool {
|
||||
for( i in this ) {
|
||||
if( predicate(i) )
|
||||
break true
|
||||
@ -169,12 +168,12 @@ fun Iterable.any(predicate): Bool {
|
||||
}
|
||||
|
||||
/* Return true if all elements match the predicate. */
|
||||
fun Iterable.all(predicate): Bool {
|
||||
fun Iterable.all<T>(predicate: (T)->Bool): Bool {
|
||||
!any { !predicate(it) }
|
||||
}
|
||||
|
||||
/* Sum all elements; returns null for empty collections. */
|
||||
fun Iterable.sum() {
|
||||
fun Iterable.sum<T>(): T? {
|
||||
val i: Iterator = iterator()
|
||||
if( i.hasNext() ) {
|
||||
var result = i.next()
|
||||
@ -185,7 +184,7 @@ fun Iterable.sum() {
|
||||
}
|
||||
|
||||
/* Sum mapped values of elements; returns null for empty collections. */
|
||||
fun Iterable.sumOf(f) {
|
||||
fun Iterable.sumOf<T,R>(f: (T)->R): R? {
|
||||
val i: Iterator = iterator()
|
||||
if( i.hasNext() ) {
|
||||
var result = f(i.next())
|
||||
@ -196,7 +195,7 @@ fun Iterable.sumOf(f) {
|
||||
}
|
||||
|
||||
/* Minimum value of the given function applied to elements of the collection. */
|
||||
fun Iterable.minOf( lambda ) {
|
||||
fun Iterable.minOf<T,R>(lambda: (T)->R): R {
|
||||
val i: Iterator = iterator()
|
||||
var minimum = lambda( i.next() )
|
||||
while( i.hasNext() ) {
|
||||
@ -207,7 +206,7 @@ fun Iterable.minOf( lambda ) {
|
||||
}
|
||||
|
||||
/* Maximum value of the given function applied to elements of the collection. */
|
||||
fun Iterable.maxOf( lambda ) {
|
||||
fun Iterable.maxOf<T,R>(lambda: (T)->R): R {
|
||||
val i: Iterator = iterator()
|
||||
var maximum = lambda( i.next() )
|
||||
while( i.hasNext() ) {
|
||||
@ -218,18 +217,18 @@ fun Iterable.maxOf( lambda ) {
|
||||
}
|
||||
|
||||
/* Return elements sorted by natural order. */
|
||||
fun Iterable.sorted() {
|
||||
fun Iterable.sorted<T>(): List<T> {
|
||||
sortedWith { a, b -> a <=> b }
|
||||
}
|
||||
|
||||
/* Return elements sorted by the key selector. */
|
||||
fun Iterable.sortedBy(predicate) {
|
||||
fun Iterable.sortedBy<T,R>(predicate: (T)->R): List<T> {
|
||||
sortedWith { a, b -> predicate(a) <=> predicate(b) }
|
||||
}
|
||||
|
||||
/* Return a shuffled copy of the iterable as a list. */
|
||||
fun Iterable.shuffled() {
|
||||
val list: List = toList
|
||||
fun Iterable.shuffled<T>(): List<T> {
|
||||
val list: List<T> = toList()
|
||||
list.shuffle()
|
||||
list
|
||||
}
|
||||
@ -238,8 +237,8 @@ fun Iterable.shuffled() {
|
||||
Returns a single list of all elements from all collections in the given collection.
|
||||
@return List
|
||||
*/
|
||||
fun Iterable.flatten() {
|
||||
var result: List = List()
|
||||
fun Iterable.flatten<T>(): List<T> {
|
||||
var result: List<T> = List()
|
||||
forEach { i ->
|
||||
i.forEach { result += it }
|
||||
}
|
||||
@ -250,8 +249,8 @@ fun Iterable.flatten() {
|
||||
Returns a single list of all elements yielded from results of transform function being
|
||||
invoked on each element of original collection.
|
||||
*/
|
||||
fun Iterable.flatMap(transform): List {
|
||||
val mapped: List = map(transform)
|
||||
fun Iterable.flatMap<T,R>(transform: (T)->Iterable<R>): List<R> {
|
||||
val mapped: List<Iterable<R>> = map(transform)
|
||||
mapped.flatten()
|
||||
}
|
||||
|
||||
@ -268,26 +267,26 @@ override fun List.toString() {
|
||||
}
|
||||
|
||||
/* Sort list in-place by key selector. */
|
||||
fun List.sortBy(predicate) {
|
||||
fun List.sortBy<T,R>(predicate: (T)->R): Void {
|
||||
sortWith { a, b -> predicate(a) <=> predicate(b) }
|
||||
}
|
||||
|
||||
/* Sort list in-place by natural order. */
|
||||
fun List.sort() {
|
||||
fun List.sort<T>(): Void {
|
||||
sortWith { a, b -> a <=> b }
|
||||
}
|
||||
|
||||
/* Print this exception and its stack trace to standard output. */
|
||||
fun Exception.printStackTrace() {
|
||||
fun Exception.printStackTrace(): Void {
|
||||
println(this)
|
||||
for( entry in stackTrace )
|
||||
println("\tat "+entry.toString())
|
||||
}
|
||||
|
||||
/* Compile this string into a regular expression. */
|
||||
val String.re get() = Regex(this)
|
||||
val String.re: Regex get() = Regex(this)
|
||||
|
||||
fun TODO(message=null) {
|
||||
fun TODO(message: Object?=null): Void {
|
||||
throw "not implemented"
|
||||
}
|
||||
|
||||
@ -306,31 +305,31 @@ enum DelegateAccess {
|
||||
Implementing this interface is optional as Lyng uses dynamic dispatch,
|
||||
but it is recommended for documentation and clarity.
|
||||
*/
|
||||
interface Delegate {
|
||||
interface Delegate<T,ThisRefType=Void> {
|
||||
/* Called when a delegated 'val' or 'var' is read. */
|
||||
fun getValue(thisRef, name) = TODO("delegate getter is not implemented")
|
||||
fun getValue(thisRef: ThisRefType, name: String): T = TODO("delegate getter is not implemented")
|
||||
|
||||
/* Called when a delegated 'var' is written. */
|
||||
fun setValue(thisRef, name, newValue) = TODO("delegate setter is not implemented")
|
||||
fun setValue(thisRef: ThisRefType, name: String, newValue: T): Void = TODO("delegate setter is not implemented")
|
||||
|
||||
/* Called when a delegated function is invoked. */
|
||||
fun invoke(thisRef, name, args...) = TODO("delegate invoke is not implemented")
|
||||
fun invoke(thisRef: ThisRefType, name: String, args...): Object = TODO("delegate invoke is not implemented")
|
||||
|
||||
/*
|
||||
Called once during initialization to configure or validate the delegate.
|
||||
Should return the delegate object to be used (usually 'this').
|
||||
*/
|
||||
fun bind(name, access, thisRef) = this
|
||||
fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object = this
|
||||
}
|
||||
|
||||
/*
|
||||
Executes the block with `this` set to self and
|
||||
returns what the block returns.
|
||||
*/
|
||||
fun with(self, block) {
|
||||
fun with<T,R>(self: T, block: T.()->R): R {
|
||||
var result = Unset
|
||||
self.apply { result = block() }
|
||||
result
|
||||
result as R
|
||||
}
|
||||
|
||||
/*
|
||||
@ -338,19 +337,19 @@ fun with(self, block) {
|
||||
The provided creator lambda is called once on the first access to compute the value.
|
||||
Can only be used with 'val' properties.
|
||||
*/
|
||||
class lazy(creatorParam) : Delegate {
|
||||
private val creator = creatorParam
|
||||
class lazy<T>(creatorParam: Object.()->T) : Delegate<T,Object> {
|
||||
private val creator: Object.()->T = creatorParam
|
||||
private var value = Unset
|
||||
|
||||
override fun bind(name, access, thisRef) {
|
||||
override fun bind(name: String, access: DelegateAccess, thisRef: Object): Object {
|
||||
if (access.toString() != "DelegateAccess.Val") throw "lazy delegate can only be used with 'val'"
|
||||
this
|
||||
}
|
||||
|
||||
override fun getValue(thisRef, name) {
|
||||
override fun getValue(thisRef: Object, name: String): T {
|
||||
if (value == Unset)
|
||||
value = with(thisRef,creator)
|
||||
value
|
||||
value as T
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ better than even in C++ ;)
|
||||
|
||||
```lyng
|
||||
fun t(x) {
|
||||
// x is Object, and x is nullable
|
||||
// x is Object (non-null)
|
||||
println(x)
|
||||
}
|
||||
|
||||
@ -117,6 +117,10 @@ Notes and open questions to answer in this spec:
|
||||
|
||||
Not null by default (Object), must be specified with `?` suffix. We use Kotlin-style `!!` for non-null assertion. Therefore we check nullability at compile time, and we throw NPE only at `x!!` or `obj as X` (if obj is nullable, it is same as `obj!! as X`).
|
||||
|
||||
Return type inference and nullability:
|
||||
- If any branch or return expression is nullable, the inferred return type is nullable.
|
||||
- This is independent of whether `return` is used or implicit last-expression rules apply.
|
||||
|
||||
- Default type of untyped values: If a parameter has no type and no default, is it Object? (dynamic), or a new top type?
|
||||
|
||||
Lets discuss in more details. For example:
|
||||
@ -199,12 +203,22 @@ square("3.14")
|
||||
|
||||
- Generics runtime model: Are type params reified via hidden Class args always, or only when used (T::class, T is ...)? How does this interact with Kotlin interop?
|
||||
|
||||
I think we can omit if not used. For kotlin interop: if the class has at least one `extern` symbol, that means native implementation, we always include type parameters, to kotlin implementation can rely on it.
|
||||
Type params are erased by default. Hidden `Class` args are only injected when a type parameter is used in a reified way (`T::class`, `T is`, `is T`, `as T`) or when the class has at least one `extern` symbol (so host implementations can rely on them). Otherwise `T` is compile-time only and runtime uses `Object`.
|
||||
|
||||
- Variance syntax:
|
||||
- Declaration-site only, Kotlin-style: `out` (covariant) and `in` (contravariant).
|
||||
- Example: `class Box<out T>`, `class Sink<in T>`.
|
||||
- Bounds remain `T: A & B` or `T: A | B`.
|
||||
|
||||
- Member access rules: If a variable is Object (dynamic), is member access a compile-time error, or allowed with fallback (which we are trying to remove)? If error, do we require explicit cast first?
|
||||
|
||||
Compile time error unless it is an Object own method. Let's force rewriting existing code in favor of explicit casts. It will repay itself: I laready have a project on Lyng that suffers from implicit casts har to trace errors.
|
||||
|
||||
No runtime lookups or fallbacks:
|
||||
- All symbol and member resolution must be done at compile time.
|
||||
- If an extension is not known at compile time (not imported or declared before use), it is a compile-time error.
|
||||
- Runtime lookup is only possible via explicit reflection helpers.
|
||||
|
||||
Example:
|
||||
```lyng
|
||||
fun f(x) { // x: Object
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user