Compare commits
3 Commits
51b397686d
...
54c6fca0e8
| Author | SHA1 | Date | |
|---|---|---|---|
| 54c6fca0e8 | |||
| c5bf4e5039 | |||
| c9da0b256f |
@ -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>()
|
||||||
|
|||||||
@ -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 rvalue = parseExpressionLevel(level + 1)
|
val res = if (opToken.type == Token.Type.AS || opToken.type == Token.Type.ASNULL) {
|
||||||
?: throw ScriptError(opToken.pos, "Expecting expression")
|
val (typeDecl, _) = parseTypeExpressionWithMini()
|
||||||
|
val typeRef = typeDeclToTypeRef(typeDecl, opToken.pos)
|
||||||
val res = op.generate(opToken.pos, lvalue!!, rvalue)
|
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) {
|
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
|
||||||
@ -2511,9 +2923,24 @@ class Compiler(
|
|||||||
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 {
|
} else {
|
||||||
val r = parseArgs(expectedReceiver)
|
if (withReceiver != null) {
|
||||||
detectedBlockArgument = r.second
|
val parsedArgs = parseArgsNoTailBlock().toMutableList()
|
||||||
r.first
|
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()
|
val implicitThisTypeName = currentImplicitThisTypeName()
|
||||||
return when (left) {
|
return when (left) {
|
||||||
@ -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()
|
||||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
classCtx?.typeParamDecls = typeParamDecls
|
||||||
parseArgsDeclaration(isClassDeclaration = true)
|
val classTypeParams = typeParamDecls.map { it.name }.toSet()
|
||||||
else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
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)
|
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
@ -3470,18 +3929,27 @@ 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>()
|
||||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
pendingTypeParamStack.add(classTypeParams)
|
||||||
do {
|
try {
|
||||||
val baseId = cc.requireToken(Token.Type.ID, "base class name expected")
|
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||||
resolutionSink?.reference(baseId.value, baseId.pos)
|
do {
|
||||||
var argsList: List<ParsedArgument>? = null
|
val (baseDecl, _) = parseSimpleTypeExpressionWithMini()
|
||||||
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
|
val baseName = when (baseDecl) {
|
||||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
is TypeDecl.Simple -> baseDecl.name
|
||||||
// Parse args without consuming any following block so that a class body can follow safely
|
is TypeDecl.Generic -> baseDecl.name
|
||||||
argsList = parseArgsNoTailBlock()
|
else -> throw ScriptError(cc.currentPos(), "base class name expected")
|
||||||
}
|
}
|
||||||
baseSpecs += BaseSpec(baseId.value, argsList)
|
var argsList: List<ParsedArgument>? = null
|
||||||
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
|
// 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)
|
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()
|
||||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
val typeParams = typeParamDecls.map { it.name }.toSet()
|
||||||
cc.nextNonWhitespace() // consume (
|
pendingTypeParamStack.add(typeParams)
|
||||||
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
val argsDeclaration: ArgsDeclaration
|
||||||
} else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
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
|
if (typeParamDecls.isNotEmpty() && declKind != SymbolKind.MEMBER) {
|
||||||
val returnTypeMini: MiniTypeRef? = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
|
currentGenericFunctionDecls()[name] = GenericFunctionDecl(typeParamDecls, argsDeclaration.params, nameStartPos)
|
||||||
parseTypeDeclarationWithMini().second
|
}
|
||||||
} else null
|
|
||||||
|
// Optional return type
|
||||||
|
returnTypeMini = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
|
||||||
|
parseTypeDeclarationWithMini().second
|
||||||
|
} 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,
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user