Compare commits

...

6 Commits

32 changed files with 1557 additions and 51 deletions

View File

@ -20,13 +20,12 @@
set -e set -e
echo "publishing all artifacts" echo "publishing all artifacts"
echo echo
./gradlew publishToMavenLocal ./gradlew publishToMavenLocal publish buildInstallablePlugin
./gradlew publish
echo #echo
echo "Creating plugin" #echo "Creating plugin"
echo #echo
./gradlew buildInstallablePlugin #./gradlew buildInstallablePlugin
echo echo
echo "building CLI tools" echo "building CLI tools"

View File

@ -1,8 +1,8 @@
# Array # Array
It's an interface if the [Collection] that provides indexing access, like `array[3] = 0`. It's an interface if the [Collection] that provides indexing access, like `array[3] = 0`.
Array therefore implements [Iterable] too. The well known implementatino of the `Array` is Array therefore implements [Iterable] too. Well known implementations of `Array` are
[List]. [List] and [ImmutableList].
Array adds the following methods: Array adds the following methods:
@ -35,3 +35,4 @@ To pre-sort and array use `Iterable.sorted*` or in-place `List.sort*` families,
[Collection]: Collection.md [Collection]: Collection.md
[Iterable]: Iterable.md [Iterable]: Iterable.md
[List]: List.md [List]: List.md
[ImmutableList]: ImmutableList.md

View File

@ -6,6 +6,12 @@ Is a [Iterable] with known `size`, a finite [Iterable]:
val size val size
} }
`Collection` is a read/traversal contract shared by mutable and immutable collections.
Concrete collection classes:
- Mutable: [List], [Set], [Map]
- Immutable: [ImmutableList], [ImmutableSet], [ImmutableMap]
| name | description | | name | description |
|------------------------|------------------------------------------------------| |------------------------|------------------------------------------------------|
@ -16,4 +22,8 @@ See [List], [Set], [Iterable] and [Efficient Iterables in Kotlin Interop](Effici
[Iterable]: Iterable.md [Iterable]: Iterable.md
[List]: List.md [List]: List.md
[Set]: Set.md [Set]: Set.md
[Map]: Map.md
[ImmutableList]: ImmutableList.md
[ImmutableSet]: ImmutableSet.md
[ImmutableMap]: ImmutableMap.md

37
docs/ImmutableList.md Normal file
View File

@ -0,0 +1,37 @@
# ImmutableList built-in class
`ImmutableList` is an immutable, indexable list value.
It implements [Array], therefore [Collection] and [Iterable].
Use it when API contracts require a list that cannot be mutated through aliases.
## Creating
val a = ImmutableList(1,2,3)
val b = [1,2,3].toImmutable()
val c = (1..3).toImmutableList()
>>> void
## Converting
val i = ImmutableList(1,2,3)
val m = i.toMutable()
m += 4
assertEquals( ImmutableList(1,2,3), i )
assertEquals( [1,2,3,4], m )
>>> void
## Members
| name | meaning |
|---------------|-----------------------------------------|
| `size` | number of elements |
| `[index]` | element access by index |
| `[Range]` | immutable slice |
| `+` | append element(s), returns new immutable list |
| `-` | remove element(s), returns new immutable list |
| `toMutable()` | create mutable copy |
[Array]: Array.md
[Collection]: Collection.md
[Iterable]: Iterable.md

36
docs/ImmutableMap.md Normal file
View File

@ -0,0 +1,36 @@
# ImmutableMap built-in class
`ImmutableMap` is an immutable map of key-value pairs.
It implements [Collection] and [Iterable] of [MapEntry].
## Creating
val a = ImmutableMap("a" => 1, "b" => 2)
val b = Map("a" => 1, "b" => 2).toImmutable()
val c = ["a" => 1, "b" => 2].toImmutableMap
>>> void
## Converting
val i = ImmutableMap("a" => 1)
val m = i.toMutable()
m["a"] = 2
assertEquals( 1, i["a"] )
assertEquals( 2, m["a"] )
>>> void
## Members
| name | meaning |
|-----------------|------------------------------------------|
| `size` | number of entries |
| `[key]` | get value by key, or `null` if absent |
| `getOrNull(key)`| same as `[key]` |
| `keys` | list of keys |
| `values` | list of values |
| `+` | merge (rightmost wins), returns new immutable map |
| `toMutable()` | create mutable copy |
[Collection]: Collection.md
[Iterable]: Iterable.md
[MapEntry]: Map.md

34
docs/ImmutableSet.md Normal file
View File

@ -0,0 +1,34 @@
# ImmutableSet built-in class
`ImmutableSet` is an immutable set of unique elements.
It implements [Collection] and [Iterable].
## Creating
val a = ImmutableSet(1,2,3)
val b = Set(1,2,3).toImmutable()
val c = [1,2,3].toImmutableSet
>>> void
## Converting
val i = ImmutableSet(1,2,3)
val m = i.toMutable()
m += 4
assertEquals( ImmutableSet(1,2,3), i )
assertEquals( Set(1,2,3,4), m )
>>> void
## Members
| name | meaning |
|---------------|-----------------------------------------------------|
| `size` | number of elements |
| `contains(x)` | membership test |
| `+`, `union` | union, returns new immutable set |
| `-`, `subtract` | subtraction, returns new immutable set |
| `*`, `intersect` | intersection, returns new immutable set |
| `toMutable()` | create mutable copy |
[Collection]: Collection.md
[Iterable]: Iterable.md

View File

@ -147,12 +147,15 @@ Search for the first element that satisfies the given predicate:
| fun/method | description | | fun/method | description |
|------------------------|---------------------------------------------------------------------------------| |------------------------|---------------------------------------------------------------------------------|
| toList() | create a list from iterable | | toList() | create a list from iterable |
| toImmutableList() | create an immutable list from iterable |
| toSet() | create a set from iterable | | toSet() | create a set from iterable |
| toImmutableSet | create an immutable set from iterable |
| contains(i) | check that iterable contains `i` | | contains(i) | check that iterable contains `i` |
| `i in iterable` | same as `contains(i)` | | `i in iterable` | same as `contains(i)` |
| isEmpty() | check iterable is empty | | isEmpty() | check iterable is empty |
| forEach(f) | call f for each element | | forEach(f) | call f for each element |
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) | | toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
| toImmutableMap | create an immutable map from list of key-value pairs |
| any(p) | true if any element matches predicate `p` | | any(p) | true if any element matches predicate `p` |
| all(p) | true if all elements match predicate `p` | | all(p) | true if all elements match predicate `p` |
| map(f) | create a list of values returned by `f` called for each element of the iterable | | map(f) | create a list of values returned by `f` called for each element of the iterable |
@ -206,16 +209,20 @@ For high-performance Kotlin-side interop and custom iterable implementation deta
## Implemented in classes: ## Implemented in classes:
- [List], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [RingBuffer] - [List], [ImmutableList], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [ImmutableSet], [Map], [ImmutableMap], [RingBuffer]
[Collection]: Collection.md [Collection]: Collection.md
[List]: List.md [List]: List.md
[ImmutableList]: ImmutableList.md
[Flow]: parallelism.md#flow [Flow]: parallelism.md#flow
[Range]: Range.md [Range]: Range.md
[Set]: Set.md [Set]: Set.md
[ImmutableSet]: ImmutableSet.md
[Map]: Map.md
[ImmutableMap]: ImmutableMap.md
[RingBuffer]: RingBuffer.md [RingBuffer]: RingBuffer.md

View File

@ -1,6 +1,7 @@
# List built-in class # List built-in class
Mutable list of any objects. Mutable list of any objects.
For immutable list values, see [ImmutableList].
It's class in Lyng is `List`: It's class in Lyng is `List`:
@ -196,4 +197,5 @@ It inherits from [Iterable] too and thus all iterable methods are applicable to
[Range]: Range.md [Range]: Range.md
[Iterable]: Iterable.md [Iterable]: Iterable.md
[ImmutableList]: ImmutableList.md

View File

@ -3,6 +3,7 @@
Map is a mutable collection of key-value pairs, where keys are unique. You can create maps in two ways: Map is a mutable collection of key-value pairs, where keys are unique. You can create maps in two ways:
- with the constructor `Map(...)` or `.toMap()` helpers; and - with the constructor `Map(...)` or `.toMap()` helpers; and
- with map literals using braces: `{ "key": value, id: expr, id: }`. - with map literals using braces: `{ "key": value, id: expr, id: }`.
For immutable map values, see [ImmutableMap].
When constructing from a list, each list item must be a [Collection] with exactly 2 elements, for example, a [List]. When constructing from a list, each list item must be a [Collection] with exactly 2 elements, for example, a [List].
@ -177,3 +178,4 @@ Notes:
- When you need computed or non-string keys, use the constructor form `Map(...)`, map literals with computed keys (if supported), or build entries with `=>` and then merge. - When you need computed or non-string keys, use the constructor form `Map(...)`, map literals with computed keys (if supported), or build entries with `=>` and then merge.
[Collection](Collection.md) [Collection](Collection.md)
[ImmutableMap]: ImmutableMap.md

View File

@ -1,7 +1,8 @@
# List built-in class # Set built-in class
Mutable set of any objects: a group of different objects, no repetitions. Mutable set of any objects: a group of different objects, no repetitions.
Sets are not ordered, order of appearance does not matter. Sets are not ordered, order of appearance does not matter.
For immutable set values, see [ImmutableSet].
val set = Set(1,2,3, "foo") val set = Set(1,2,3, "foo")
assert( 1 in set ) assert( 1 in set )
@ -92,3 +93,4 @@ Also, it inherits methods from [Iterable].
[Range]: Range.md [Range]: Range.md
[ImmutableSet]: ImmutableSet.md

View File

@ -14,7 +14,7 @@ __Other documents to read__ maybe after this one:
- [time](time.md) and [parallelism](parallelism.md) - [time](time.md) and [parallelism](parallelism.md)
- [parallelism] - multithreaded code, coroutines, etc. - [parallelism] - multithreaded code, coroutines, etc.
- Some class - Some class
references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md), [Array], [RingBuffer], [Buffer]. references: [List], [ImmutableList], [Set], [ImmutableSet], [Map], [ImmutableMap], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md), [Array], [RingBuffer], [Buffer].
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and - Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and
loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples) loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
@ -785,6 +785,7 @@ Lyng has built-in mutable array class `List` with simple literals:
[List] is an implementation of the type `Array`, and through it `Collection` and [Iterable]. Please read [Iterable], [List] is an implementation of the type `Array`, and through it `Collection` and [Iterable]. Please read [Iterable],
many collection based methods are implemented there. many collection based methods are implemented there.
For immutable list values, use `list.toImmutable()` and [ImmutableList].
Lists can contain any type of objects, lists too: Lists can contain any type of objects, lists too:
@ -967,6 +968,7 @@ Set are unordered collection of unique elements, see [Set]. Sets are [Iterable]
>>> void >>> void
Please see [Set] for detailed description. Please see [Set] for detailed description.
For immutable set values, use `set.toImmutable()` and [ImmutableSet].
# Maps # Maps
@ -1027,6 +1029,7 @@ Notes:
- When you need computed (expression) keys or non-string keys, use `Map(...)` constructor with entries, e.g. `Map( ("a" + "b") => 1 )`, then merge with a literal if needed: `{ base: } + (computedKey => 42)`. - When you need computed (expression) keys or non-string keys, use `Map(...)` constructor with entries, e.g. `Map( ("a" + "b") => 1 )`, then merge with a literal if needed: `{ base: } + (computedKey => 42)`.
Please see the [Map] reference for a deeper guide. Please see the [Map] reference for a deeper guide.
For immutable map values, use `map.toImmutable()` and [ImmutableMap].
# Flow control operators # Flow control operators
@ -1750,6 +1753,7 @@ Lambda avoid unnecessary execution if assertion is not failed. for example:
| π | See [math](math.md) | | π | See [math](math.md) |
[List]: List.md [List]: List.md
[ImmutableList]: ImmutableList.md
[Testing]: Testing.md [Testing]: Testing.md
@ -1766,8 +1770,10 @@ Lambda avoid unnecessary execution if assertion is not failed. for example:
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary [string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
[Set]: Set.md [Set]: Set.md
[ImmutableSet]: ImmutableSet.md
[Map]: Map.md [Map]: Map.md
[ImmutableMap]: ImmutableMap.md
[Buffer]: Buffer.md [Buffer]: Buffer.md

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "1.5.1-SNAPSHOT" version = "1.5.0-SNAPSHOT"
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below

View File

@ -33,6 +33,7 @@ class ClassInstanceFieldDeclStatement(
val isMutable: Boolean, val isMutable: Boolean,
val visibility: Visibility, val visibility: Visibility,
val writeVisibility: Visibility?, val writeVisibility: Visibility?,
val typeDecl: TypeDecl?,
val isAbstract: Boolean, val isAbstract: Boolean,
val isClosed: Boolean, val isClosed: Boolean,
val isOverride: Boolean, val isOverride: Boolean,

View File

@ -169,6 +169,7 @@ class Compiler(
) )
private val typeAliases: MutableMap<String, TypeAliasDecl> = mutableMapOf() private val typeAliases: MutableMap<String, TypeAliasDecl> = mutableMapOf()
private val methodReturnTypeDeclByRef: MutableMap<ObjRef, TypeDecl> = mutableMapOf() private val methodReturnTypeDeclByRef: MutableMap<ObjRef, TypeDecl> = mutableMapOf()
private val callReturnTypeDeclByRef: MutableMap<CallRef, TypeDecl> = mutableMapOf()
private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf() private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf() private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf()
@ -211,6 +212,9 @@ class Compiler(
scopeSeedNames.add(name) scopeSeedNames.add(name)
if (record.typeDecl != null && nameTypeDecl[name] == null) { if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl nameTypeDecl[name] = record.typeDecl
if (nameObjClass[name] == null) {
resolveTypeDeclObjClass(record.typeDecl)?.let { nameObjClass[name] = it }
}
} }
val instance = record.value as? ObjInstance val instance = record.value as? ObjInstance
if (instance != null && nameObjClass[name] == null) { if (instance != null && nameObjClass[name] == null) {
@ -290,6 +294,9 @@ class Compiler(
scopeSeedNames.add(name) scopeSeedNames.add(name)
if (record.typeDecl != null && nameTypeDecl[name] == null) { if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl nameTypeDecl[name] = record.typeDecl
if (nameObjClass[name] == null) {
resolveTypeDeclObjClass(record.typeDecl)?.let { nameObjClass[name] = it }
}
} }
if (record.typeDecl != null) { if (record.typeDecl != null) {
slotTypeDeclByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = record.typeDecl slotTypeDeclByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = record.typeDecl
@ -1207,6 +1214,11 @@ class Compiler(
for ((name, record) in current.objects) { for ((name, record) in current.objects) {
if (!record.visibility.isPublic) continue if (!record.visibility.isPublic) continue
if (nameObjClass.containsKey(name)) continue if (nameObjClass.containsKey(name)) continue
val declaredClass = record.typeDecl?.let { resolveTypeDeclObjClass(it) }
if (declaredClass != null) {
nameObjClass[name] = declaredClass
continue
}
val resolved = when (val raw = record.value) { val resolved = when (val raw = record.value) {
is FrameSlotRef -> raw.peekValue() ?: raw.read() is FrameSlotRef -> raw.peekValue() ?: raw.read()
is RecordSlotRef -> raw.peekValue() ?: raw.read() is RecordSlotRef -> raw.peekValue() ?: raw.read()
@ -2518,6 +2530,9 @@ class Compiler(
} else { } else {
val rvalue = parseExpressionLevel(level + 1) val rvalue = parseExpressionLevel(level + 1)
?: throw ScriptError(opToken.pos, "Expecting expression") ?: throw ScriptError(opToken.pos, "Expecting expression")
if (opToken.type == Token.Type.PLUSASSIGN) {
checkCollectionPlusAssignTypes(lvalue!!, rvalue, opToken.pos)
}
op.generate(opToken.pos, lvalue!!, rvalue) op.generate(opToken.pos, lvalue!!, rvalue)
} }
if (opToken.type == Token.Type.ASSIGN) { if (opToken.type == Token.Type.ASSIGN) {
@ -2544,6 +2559,7 @@ class Compiler(
private suspend fun parseTerm(): ObjRef? { private suspend fun parseTerm(): ObjRef? {
var operand: ObjRef? = null var operand: ObjRef? = null
var pendingCallTypeArgs: List<TypeDecl>? = null
// newlines _before_ // newlines _before_
cc.skipWsTokens() cc.skipWsTokens()
@ -2791,20 +2807,35 @@ class Compiler(
operand = parseScopeOperator(operand) operand = parseScopeOperator(operand)
} }
Token.Type.LT -> {
val parsedTypeArgs = operand
?.takeIf { isGenericCallCalleeCandidate(it) }
?.let { tryParseCallTypeArgsAfterLt() }
if (parsedTypeArgs != null) {
pendingCallTypeArgs = parsedTypeArgs
continue
}
cc.previous()
return operand
}
Token.Type.LPAREN, Token.Type.NULL_COALESCE_INVOKE -> { Token.Type.LPAREN, Token.Type.NULL_COALESCE_INVOKE -> {
operand?.let { left -> operand?.let { left ->
// this is function call from <left> // this is function call from <left>
operand = parseFunctionCall( operand = parseFunctionCall(
left, left,
false, false,
t.type == Token.Type.NULL_COALESCE_INVOKE t.type == Token.Type.NULL_COALESCE_INVOKE,
pendingCallTypeArgs
) )
pendingCallTypeArgs = null
} ?: run { } ?: run {
// Expression in parentheses // Expression in parentheses
val statement = parseStatement() ?: throw ScriptError(t.pos, "Expecting expression") val statement = parseStatement() ?: throw ScriptError(t.pos, "Expecting expression")
operand = StatementRef(statement) operand = StatementRef(statement)
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'") cc.skipTokenOfType(Token.Type.RPAREN, "missing ')'")
pendingCallTypeArgs = null
} }
} }
@ -2984,7 +3015,8 @@ class Compiler(
parseFunctionCall( parseFunctionCall(
left, left,
blockArgument = true, blockArgument = true,
isOptional = t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE isOptional = t.type == Token.Type.NULL_COALESCE_BLOCKINVOKE,
explicitTypeArgs = pendingCallTypeArgs
) )
} ?: run { } ?: run {
// Disambiguate between lambda and map literal. // Disambiguate between lambda and map literal.
@ -3011,6 +3043,54 @@ class Compiler(
} }
} }
private suspend fun tryParseCallTypeArgsAfterLt(): List<TypeDecl>? {
val savedAfterLt = cc.savePos()
return try {
val args = mutableListOf<TypeDecl>()
do {
val (argSem, _) = parseTypeExpressionWithMini()
args += argSem
val sep = cc.next()
when (sep.type) {
Token.Type.COMMA -> continue
Token.Type.GT -> break
Token.Type.SHR -> {
cc.pushPendingGT()
break
}
else -> {
cc.restorePos(savedAfterLt)
return null
}
}
} while (true)
val nextType = cc.peekNextNonWhitespace().type
if (nextType != Token.Type.LPAREN && nextType != Token.Type.NULL_COALESCE_INVOKE) {
cc.restorePos(savedAfterLt)
return null
}
args
} catch (_: ScriptError) {
cc.restorePos(savedAfterLt)
null
}
}
private fun isGenericCallCalleeCandidate(ref: ObjRef): Boolean {
val name = when (ref) {
is LocalVarRef -> ref.name
is FastLocalVarRef -> ref.name
is LocalSlotRef -> ref.name
else -> null
}
if (name != null) {
if (lookupGenericFunctionDecl(name) != null) return true
if (name.firstOrNull()?.isUpperCase() == true) return true
return false
}
return ref is ConstRef && ref.constValue is ObjClass
}
/** /**
* Parse lambda expression, leading '{' is already consumed * Parse lambda expression, leading '{' is already consumed
*/ */
@ -4131,6 +4211,22 @@ class Compiler(
is ListLiteralRef -> inferListLiteralTypeDecl(ref) is ListLiteralRef -> inferListLiteralTypeDecl(ref)
is MapLiteralRef -> inferMapLiteralTypeDecl(ref) is MapLiteralRef -> inferMapLiteralTypeDecl(ref)
is ConstRef -> inferTypeDeclFromConst(ref.constValue) is ConstRef -> inferTypeDeclFromConst(ref.constValue)
is CallRef -> {
inferCallReturnClass(ref)?.let { TypeDecl.Simple(it.className, false) }
?: run {
val targetName = when (val target = ref.target) {
is LocalVarRef -> target.name
is FastLocalVarRef -> target.name
is LocalSlotRef -> target.name
else -> null
}
if (targetName != null && targetName.firstOrNull()?.isUpperCase() == true) {
TypeDecl.Simple(targetName, false)
} else {
null
}
}
}
else -> null else -> null
} }
} }
@ -4243,7 +4339,7 @@ class Compiler(
val mapType = inferTypeDeclFromRef(entry.ref) ?: return TypeDecl.TypeAny to TypeDecl.TypeAny val mapType = inferTypeDeclFromRef(entry.ref) ?: return TypeDecl.TypeAny to TypeDecl.TypeAny
if (mapType is TypeDecl.Generic) { if (mapType is TypeDecl.Generic) {
val base = mapType.name.substringAfterLast('.') val base = mapType.name.substringAfterLast('.')
if (base == "Map") { if (base == "Map" || base == "ImmutableMap") {
val k = mapType.args.getOrNull(0) ?: TypeDecl.TypeAny val k = mapType.args.getOrNull(0) ?: TypeDecl.TypeAny
val v = mapType.args.getOrNull(1) ?: TypeDecl.TypeAny val v = mapType.args.getOrNull(1) ?: TypeDecl.TypeAny
addKey(k) addKey(k)
@ -4278,13 +4374,73 @@ class Compiler(
if (listType == TypeDecl.TypeAny || listType == TypeDecl.TypeNullableAny) return listType if (listType == TypeDecl.TypeAny || listType == TypeDecl.TypeNullableAny) return listType
if (listType is TypeDecl.Generic) { if (listType is TypeDecl.Generic) {
val base = listType.name.substringAfterLast('.') val base = listType.name.substringAfterLast('.')
if (base == "List" || base == "Array" || base == "Iterable") { if (base == "List" || base == "ImmutableList" || base == "Array" || base == "Iterable") {
return listType.args.firstOrNull() ?: TypeDecl.TypeAny return listType.args.firstOrNull() ?: TypeDecl.TypeAny
} }
} }
return TypeDecl.TypeAny return TypeDecl.TypeAny
} }
private fun inferCollectionElementType(typeDecl: TypeDecl): TypeDecl? {
val generic = typeDecl as? TypeDecl.Generic ?: return null
val base = generic.name.substringAfterLast('.')
return when (base) {
"Set", "ImmutableSet", "List", "ImmutableList", "Iterable", "Collection", "Array" -> generic.args.firstOrNull()
else -> null
}
}
private fun typeDeclSubtypeOf(arg: TypeDecl, param: TypeDecl): Boolean {
if (param == TypeDecl.TypeAny || param == TypeDecl.TypeNullableAny) return true
val (argBase, argNullable) = stripNullable(arg)
val (paramBase, paramNullable) = stripNullable(param)
if (argNullable && !paramNullable) return false
if (paramBase == TypeDecl.TypeAny) return true
if (paramBase is TypeDecl.TypeVar) return true
if (argBase is TypeDecl.TypeVar) return true
if (paramBase is TypeDecl.Simple && (paramBase.name == "Object" || paramBase.name == "Obj")) return true
if (argBase is TypeDecl.Ellipsis) return typeDeclSubtypeOf(argBase.elementType, paramBase)
if (paramBase is TypeDecl.Ellipsis) return typeDeclSubtypeOf(argBase, paramBase.elementType)
return when (argBase) {
is TypeDecl.Union -> argBase.options.all { typeDeclSubtypeOf(it, paramBase) }
is TypeDecl.Intersection -> argBase.options.any { typeDeclSubtypeOf(it, paramBase) }
else -> when (paramBase) {
is TypeDecl.Union -> paramBase.options.any { typeDeclSubtypeOf(argBase, it) }
is TypeDecl.Intersection -> paramBase.options.all { typeDeclSubtypeOf(argBase, it) }
else -> {
val argClass = resolveTypeDeclObjClass(argBase) ?: return false
val paramClass = resolveTypeDeclObjClass(paramBase) ?: return false
argClass == paramClass || argClass.allParentsSet.contains(paramClass)
}
}
}
}
private fun checkCollectionPlusAssignTypes(targetRef: ObjRef, valueRef: ObjRef, pos: Pos) {
// Enforce strict compile-time element checks for declared members.
// Local vars can be inferred from literals and are allowed to widen dynamically.
if (targetRef !is FieldRef) return
val targetDeclRaw = resolveReceiverTypeDecl(targetRef) ?: return
val targetDecl = expandTypeAliases(targetDeclRaw, pos)
val targetGeneric = targetDecl as? TypeDecl.Generic ?: return
val targetBase = targetGeneric.name.substringAfterLast('.')
if (targetBase != "Set" && targetBase != "List") return
val elementRaw = targetGeneric.args.firstOrNull() ?: return
val elementDecl = expandTypeAliases(elementRaw, pos)
val valueDeclRaw = inferTypeDeclFromRef(valueRef) ?: return
val valueDecl = expandTypeAliases(valueDeclRaw, pos)
if (typeDeclSubtypeOf(valueDecl, elementDecl)) return
val sourceElementDecl = inferCollectionElementType(valueDecl)?.let { expandTypeAliases(it, pos) }
if (sourceElementDecl != null && typeDeclSubtypeOf(sourceElementDecl, elementDecl)) return
throw ScriptError(
pos,
"argument type ${typeDeclName(valueDecl)} does not match ${typeDeclName(elementDecl)} for '+='"
)
}
private fun stripNullable(type: TypeDecl): Pair<TypeDecl, Boolean> { private fun stripNullable(type: TypeDecl): Pair<TypeDecl, Boolean> {
if (type is TypeDecl.TypeNullableAny) return TypeDecl.TypeAny to true if (type is TypeDecl.TypeNullableAny) return TypeDecl.TypeAny to true
val nullable = type.isNullable val nullable = type.isNullable
@ -4335,16 +4491,41 @@ class Compiler(
else -> null else -> null
} }
private fun seedTypeDeclByName(name: String): TypeDecl? {
seedScope?.getLocalRecordDirect(name)?.typeDecl?.let { return it }
seedScope?.get(name)?.typeDecl?.let { return it }
importManager.rootScope.getLocalRecordDirect(name)?.typeDecl?.let { return it }
importManager.rootScope.get(name)?.typeDecl?.let { return it }
for (module in importedModules.asReversed()) {
module.scope.get(name)?.typeDecl?.let { return it }
}
return null
}
private fun resolveReceiverTypeDecl(ref: ObjRef): TypeDecl? { private fun resolveReceiverTypeDecl(ref: ObjRef): TypeDecl? {
return when (ref) { return when (ref) {
is LocalSlotRef -> { is LocalSlotRef -> {
val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
val ownerSlot = ref.captureOwnerSlot ?: ref.slot val ownerSlot = ref.captureOwnerSlot ?: ref.slot
slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot) slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)
?: nameTypeDecl[ref.name]
?: seedTypeDeclByName(ref.name)
}
is LocalVarRef -> nameTypeDecl[ref.name] ?: seedTypeDeclByName(ref.name)
is FastLocalVarRef -> nameTypeDecl[ref.name] ?: seedTypeDeclByName(ref.name)
is FieldRef -> {
val targetDecl = resolveReceiverTypeDecl(ref.target) ?: return null
val targetClass = resolveTypeDeclObjClass(targetDecl) ?: resolveReceiverClassForMember(ref.target)
targetClass?.getInstanceMemberOrNull(ref.name, includeAbstract = true)?.typeDecl?.let { return it }
classFieldTypesByName[targetClass?.className]?.get(ref.name)
?.let { return TypeDecl.Simple(it.className, false) }
when (targetDecl) {
TypeDecl.TypeAny, TypeDecl.TypeNullableAny -> null
else -> TypeDecl.TypeVar("${typeDeclName(targetDecl)}.${ref.name}", false)
}
} }
is LocalVarRef -> nameTypeDecl[ref.name]
is FastLocalVarRef -> nameTypeDecl[ref.name]
is MethodCallRef -> methodReturnTypeDeclByRef[ref] is MethodCallRef -> methodReturnTypeDeclByRef[ref]
is CallRef -> callReturnTypeDeclByRef[ref]
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) } is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
else -> null else -> null
} }
@ -4361,6 +4542,8 @@ class Compiler(
} else { } else {
slotTypeByScopeId[ownerScopeId]?.get(ownerSlot) slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
?: slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)?.let { resolveTypeDeclObjClass(it) } ?: slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)?.let { resolveTypeDeclObjClass(it) }
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
?: seedTypeDeclByName(ref.name)?.let { resolveTypeDeclObjClass(it) }
?: knownClass ?: knownClass
} }
?: resolveClassByName(ref.name) ?: resolveClassByName(ref.name)
@ -4486,6 +4669,58 @@ class Compiler(
name == "next" && receiver is TypeDecl.Generic && base == "Iterator" -> { name == "next" && receiver is TypeDecl.Generic && base == "Iterator" -> {
receiver.args.firstOrNull() receiver.args.firstOrNull()
} }
name == "toImmutableList" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Array" || base == "List" || base == "ImmutableList") -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("ImmutableList", listOf(arg), false)
}
name == "toList" && receiver is TypeDecl.Generic && (base == "ImmutableList" || base == "List") -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("List", listOf(arg), false)
}
name == "toMutable" && receiver is TypeDecl.Generic && base == "ImmutableList" -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("List", listOf(arg), false)
}
name == "toImmutable" && receiver is TypeDecl.Generic && base == "List" -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("ImmutableList", listOf(arg), false)
}
name == "toImmutableSet" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Set" || base == "ImmutableSet") -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("ImmutableSet", listOf(arg), false)
}
name == "toSet" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Set" || base == "ImmutableSet") -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("Set", listOf(arg), false)
}
name == "toMutable" && receiver is TypeDecl.Generic && base == "ImmutableSet" -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("Set", listOf(arg), false)
}
name == "toImmutable" && receiver is TypeDecl.Generic && base == "Set" -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("ImmutableSet", listOf(arg), false)
}
name == "toImmutableMap" && receiver is TypeDecl.Generic && base == "Iterable" -> {
TypeDecl.Generic("ImmutableMap", listOf(TypeDecl.TypeAny, TypeDecl.TypeAny), false)
}
name == "toMap" && receiver is TypeDecl.Generic && base == "Iterable" -> {
TypeDecl.Generic("Map", listOf(TypeDecl.TypeAny, TypeDecl.TypeAny), false)
}
name == "toMutable" && receiver is TypeDecl.Generic && base == "ImmutableMap" -> {
val args = receiver.args.ifEmpty { listOf(TypeDecl.TypeAny, TypeDecl.TypeAny) }
TypeDecl.Generic("Map", args, false)
}
name == "toImmutable" && receiver is TypeDecl.Generic && base == "Map" -> {
val args = receiver.args.ifEmpty { listOf(TypeDecl.TypeAny, TypeDecl.TypeAny) }
TypeDecl.Generic("ImmutableMap", args, false)
}
name == "toImmutable" && base == "List" -> TypeDecl.Simple("ImmutableList", false)
name == "toMutable" && base == "ImmutableList" -> TypeDecl.Simple("List", false)
name == "toImmutable" && base == "Set" -> TypeDecl.Simple("ImmutableSet", false)
name == "toMutable" && base == "ImmutableSet" -> TypeDecl.Simple("Set", false)
name == "toImmutable" && base == "Map" -> TypeDecl.Simple("ImmutableMap", false)
name == "toMutable" && base == "ImmutableMap" -> TypeDecl.Simple("Map", false)
else -> null else -> null
} }
} }
@ -4556,6 +4791,10 @@ class Compiler(
"matches" -> ObjBool.type "matches" -> ObjBool.type
"toInt", "toInt",
"toEpochSeconds" -> ObjInt.type "toEpochSeconds" -> ObjInt.type
"toImmutableList" -> ObjImmutableList.type
"toImmutableSet" -> ObjImmutableSet.type
"toImmutableMap" -> ObjImmutableMap.type
"toImmutable" -> Obj.rootObjectType
"toMutable" -> ObjMutableBuffer.type "toMutable" -> ObjMutableBuffer.type
"seq" -> ObjFlow.type "seq" -> ObjFlow.type
"encode" -> ObjBitBuffer.type "encode" -> ObjBitBuffer.type
@ -4567,6 +4806,18 @@ class Compiler(
if (targetClass == null) return null if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return ObjDynamic.type if (targetClass == ObjDynamic.type) return ObjDynamic.type
classFieldTypesByName[targetClass.className]?.get(name)?.let { return it } classFieldTypesByName[targetClass.className]?.get(name)?.let { return it }
var hasUntypedMember = false
targetClass.getInstanceMemberOrNull(name, includeAbstract = true)?.let { member ->
member.typeDecl?.let { declaredType ->
when (declaredType) {
TypeDecl.TypeAny, TypeDecl.TypeNullableAny -> return Obj.rootObjectType
else -> {
resolveTypeDeclObjClass(declaredType)?.let { return it }
}
}
}
hasUntypedMember = true
}
enumEntriesByName[targetClass.className]?.let { entries -> enumEntriesByName[targetClass.className]?.let { entries ->
return when { return when {
name == "entries" -> ObjList.type name == "entries" -> ObjList.type
@ -4653,6 +4904,9 @@ class Compiler(
if (targetClass == ObjRegex.type && name == "pattern") { if (targetClass == ObjRegex.type && name == "pattern") {
return ObjString.type return ObjString.type
} }
if (hasUntypedMember) {
return ObjDynamic.type
}
return null return null
} }
@ -4666,6 +4920,10 @@ class Compiler(
if (left is LocalSlotRef && left.name == "scope") return if (left is LocalSlotRef && left.name == "scope") return
val receiverClass = resolveReceiverClassForMember(left) val receiverClass = resolveReceiverClassForMember(left)
if (receiverClass == null) { if (receiverClass == null) {
val receiverDecl = resolveReceiverTypeDecl(left)
if (receiverDecl != null && receiverDecl != TypeDecl.TypeAny && receiverDecl != TypeDecl.TypeNullableAny) {
return
}
if (isAllowedObjectMember(memberName)) return if (isAllowedObjectMember(memberName)) return
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName") throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
} }
@ -5362,7 +5620,8 @@ class Compiler(
private suspend fun parseFunctionCall( private suspend fun parseFunctionCall(
left: ObjRef, left: ObjRef,
blockArgument: Boolean, blockArgument: Boolean,
isOptional: Boolean isOptional: Boolean,
explicitTypeArgs: List<TypeDecl>? = null
): ObjRef { ): ObjRef {
var detectedBlockArgument = blockArgument var detectedBlockArgument = blockArgument
val expectedReceiver = tailBlockReceiverType(left) val expectedReceiver = tailBlockReceiverType(left)
@ -5403,7 +5662,9 @@ class Compiler(
val result = when (left) { val result = when (left) {
is ImplicitThisMemberRef -> is ImplicitThisMemberRef ->
if (left.methodId == null && left.fieldId != null) { if (left.methodId == null && left.fieldId != null) {
CallRef(left, args, detectedBlockArgument, isOptional) CallRef(left, args, detectedBlockArgument, isOptional).also { callRef ->
applyExplicitCallTypeArgs(callRef, explicitTypeArgs)
}
} else { } else {
ImplicitThisMethodCallRef( ImplicitThisMethodCallRef(
left.name, left.name,
@ -5436,7 +5697,9 @@ class Compiler(
checkFunctionTypeCallArity(left, args, left.pos()) checkFunctionTypeCallArity(left, args, left.pos())
checkFunctionTypeCallTypes(left, args, left.pos()) checkFunctionTypeCallTypes(left, args, left.pos())
checkGenericBoundsAtCall(left.name, args, left.pos()) checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional) CallRef(left, args, detectedBlockArgument, isOptional).also { callRef ->
applyExplicitCallTypeArgs(callRef, explicitTypeArgs)
}
} }
} }
is LocalSlotRef -> { is LocalSlotRef -> {
@ -5460,14 +5723,30 @@ class Compiler(
checkFunctionTypeCallArity(left, args, left.pos()) checkFunctionTypeCallArity(left, args, left.pos())
checkFunctionTypeCallTypes(left, args, left.pos()) checkFunctionTypeCallTypes(left, args, left.pos())
checkGenericBoundsAtCall(left.name, args, left.pos()) checkGenericBoundsAtCall(left.name, args, left.pos())
CallRef(left, args, detectedBlockArgument, isOptional) CallRef(left, args, detectedBlockArgument, isOptional).also { callRef ->
applyExplicitCallTypeArgs(callRef, explicitTypeArgs)
}
} }
} }
else -> CallRef(left, args, detectedBlockArgument, isOptional) else -> CallRef(left, args, detectedBlockArgument, isOptional).also { callRef ->
applyExplicitCallTypeArgs(callRef, explicitTypeArgs)
}
} }
return result return result
} }
private fun applyExplicitCallTypeArgs(callRef: CallRef, explicitTypeArgs: List<TypeDecl>?) {
if (explicitTypeArgs.isNullOrEmpty()) return
val baseName = when (val target = callRef.target) {
is LocalVarRef -> target.name
is FastLocalVarRef -> target.name
is LocalSlotRef -> target.name
is ConstRef -> (target.constValue as? ObjClass)?.className
else -> null
} ?: return
callReturnTypeDeclByRef[callRef] = TypeDecl.Generic(baseName, explicitTypeArgs, isNullable = false)
}
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? { private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null
val ref = stmt.ref val ref = stmt.ref
@ -8025,8 +8304,11 @@ class Compiler(
"Bool" -> ObjBool.type "Bool" -> ObjBool.type
"Char" -> ObjChar.type "Char" -> ObjChar.type
"List" -> ObjList.type "List" -> ObjList.type
"ImmutableList" -> ObjImmutableList.type
"Map" -> ObjMap.type "Map" -> ObjMap.type
"ImmutableMap" -> ObjImmutableMap.type
"Set" -> ObjSet.type "Set" -> ObjSet.type
"ImmutableSet" -> ObjImmutableSet.type
"Range", "IntRange" -> ObjRange.type "Range", "IntRange" -> ObjRange.type
"Iterator" -> ObjIterator "Iterator" -> ObjIterator
"Iterable" -> ObjIterable "Iterable" -> ObjIterable
@ -8906,6 +9188,7 @@ class Compiler(
isMutable = isMutable, isMutable = isMutable,
visibility = visibility, visibility = visibility,
writeVisibility = setterVisibility, writeVisibility = setterVisibility,
typeDecl = if (varTypeDecl == TypeDecl.TypeAny || varTypeDecl == TypeDecl.TypeNullableAny) null else varTypeDecl,
isAbstract = isAbstract, isAbstract = isAbstract,
isClosed = isClosed, isClosed = isClosed,
isOverride = isOverride, isOverride = isOverride,

View File

@ -471,6 +471,62 @@ open class Scope(
} }
} }
private fun resolvedRecordValueOrNull(record: ObjRecord): Obj? {
return when (val raw = record.value) {
is FrameSlotRef -> raw.read()
is RecordSlotRef -> raw.read()
else -> raw
}
}
private fun declaredTypeForValueInThisScope(value: Obj): TypeDecl? {
// Prefer direct bindings first.
for (record in objects.values) {
val decl = record.typeDecl ?: continue
if (resolvedRecordValueOrNull(record) === value) return decl
}
for ((_, record) in localBindings) {
val decl = record.typeDecl ?: continue
if (resolvedRecordValueOrNull(record) === value) return decl
}
// Then slots (for frame-first locals).
var i = 0
while (i < slots.size) {
val record = slots[i]
val decl = record.typeDecl
if (decl != null && resolvedRecordValueOrNull(record) === value) return decl
i++
}
return null
}
private fun declaredCollectionElementTypeForValue(value: Obj, rawName: String): TypeDecl? {
var s: Scope? = this
var hops = 0
while (s != null && hops++ < 1024) {
val decl = s.declaredTypeForValueInThisScope(value)
if (decl is TypeDecl.Generic && decl.name.substringAfterLast('.') == rawName) {
return decl.args.firstOrNull()
}
s = s.parent
}
return null
}
/**
* Best-effort lookup of the declared Set element type for a runtime set instance.
* Returns null when type info is unavailable.
*/
fun declaredSetElementTypeForValue(value: Obj): TypeDecl? =
declaredCollectionElementTypeForValue(value, "Set")
/**
* Best-effort lookup of the declared List element type for a runtime list instance.
* Returns null when type info is unavailable.
*/
fun declaredListElementTypeForValue(value: Obj): TypeDecl? =
declaredCollectionElementTypeForValue(value, "List")
internal fun applySlotPlanReset(plan: Map<String, Int>, records: Map<String, ObjRecord>) { internal fun applySlotPlanReset(plan: Map<String, Int>, records: Map<String, ObjRecord>) {
if (plan.isEmpty()) return if (plan.isEmpty()) return
slots.clear() slots.clear()

View File

@ -522,9 +522,12 @@ class Script(
addConst("Bool", ObjBool.type) addConst("Bool", ObjBool.type)
addConst("Char", ObjChar.type) addConst("Char", ObjChar.type)
addConst("List", ObjList.type) addConst("List", ObjList.type)
addConst("ImmutableList", ObjImmutableList.type)
addConst("Set", ObjSet.type) addConst("Set", ObjSet.type)
addConst("ImmutableSet", ObjImmutableSet.type)
addConst("Range", ObjRange.type) addConst("Range", ObjRange.type)
addConst("Map", ObjMap.type) addConst("Map", ObjMap.type)
addConst("ImmutableMap", ObjImmutableMap.type)
addConst("MapEntry", ObjMapEntry.type) addConst("MapEntry", ObjMapEntry.type)
@Suppress("RemoveRedundantQualifierName") @Suppress("RemoveRedundantQualifierName")
addConst("Callable", Statement.type) addConst("Callable", Statement.type)

View File

@ -4439,7 +4439,11 @@ class BytecodeCompiler(
} }
val initClass = when (localTarget?.name) { val initClass = when (localTarget?.name) {
"List" -> ObjList.type "List" -> ObjList.type
"ImmutableList" -> ObjImmutableList.type
"Map" -> ObjMap.type "Map" -> ObjMap.type
"ImmutableMap" -> ObjImmutableMap.type
"Set" -> ObjSet.type
"ImmutableSet" -> ObjImmutableSet.type
else -> null else -> null
} }
val callee = compileRefWithFallback(ref.target, null, refPosOrCurrent(ref.target)) ?: return null val callee = compileRefWithFallback(ref.target, null, refPosOrCurrent(ref.target)) ?: return null
@ -5174,6 +5178,7 @@ class BytecodeCompiler(
isMutable = stmt.isMutable, isMutable = stmt.isMutable,
visibility = stmt.visibility, visibility = stmt.visibility,
writeVisibility = stmt.writeVisibility, writeVisibility = stmt.writeVisibility,
typeDecl = stmt.typeDecl,
isTransient = stmt.isTransient, isTransient = stmt.isTransient,
isAbstract = stmt.isAbstract, isAbstract = stmt.isAbstract,
isClosed = stmt.isClosed, isClosed = stmt.isClosed,
@ -6998,7 +7003,9 @@ class BytecodeCompiler(
val slot = resolveSlot(ref) val slot = resolveSlot(ref)
val fromSlot = slot?.let { slotObjClass[it] } val fromSlot = slot?.let { slotObjClass[it] }
fromSlot fromSlot
?: slot?.let { typeDeclForSlot(it) }?.let { resolveClassFromTypeDecl(it) }
?: slotTypeByScopeId[ownerScopeId]?.get(ownerSlot) ?: slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
?: slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)?.let { resolveClassFromTypeDecl(it) }
?: nameObjClass[ref.name] ?: nameObjClass[ref.name]
?: resolveTypeNameClass(ref.name) ?: resolveTypeNameClass(ref.name)
?: slotInitClassByKey[ScopeSlotKey(ownerScopeId, ownerSlot)] ?: slotInitClassByKey[ScopeSlotKey(ownerScopeId, ownerSlot)]
@ -7016,9 +7023,14 @@ class BytecodeCompiler(
} }
val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] } val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
if (fromSlot != null) return fromSlot if (fromSlot != null) return fromSlot
val fromDirectTypeDecl = resolveDirectNameSlot(ref.name)
?.let { typeDeclForSlot(it.slot) }
?.let { resolveClassFromTypeDecl(it) }
if (fromDirectTypeDecl != null) return fromDirectTypeDecl
val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
key?.let { key?.let {
slotTypeByScopeId[it.scopeId]?.get(it.slot) slotTypeByScopeId[it.scopeId]?.get(it.slot)
?: slotTypeDeclByScopeId[it.scopeId]?.get(it.slot)?.let { decl -> resolveClassFromTypeDecl(decl) }
?: slotInitClassByKey[it] ?: slotInitClassByKey[it]
} ?: nameObjClass[ref.name] } ?: nameObjClass[ref.name]
?: resolveTypeNameClass(ref.name) ?: resolveTypeNameClass(ref.name)
@ -7029,9 +7041,14 @@ class BytecodeCompiler(
} }
val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] } val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
if (fromSlot != null) return fromSlot if (fromSlot != null) return fromSlot
val fromDirectTypeDecl = resolveDirectNameSlot(ref.name)
?.let { typeDeclForSlot(it.slot) }
?.let { resolveClassFromTypeDecl(it) }
if (fromDirectTypeDecl != null) return fromDirectTypeDecl
val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
key?.let { key?.let {
slotTypeByScopeId[it.scopeId]?.get(it.slot) slotTypeByScopeId[it.scopeId]?.get(it.slot)
?: slotTypeDeclByScopeId[it.scopeId]?.get(it.slot)?.let { decl -> resolveClassFromTypeDecl(decl) }
?: slotInitClassByKey[it] ?: slotInitClassByKey[it]
} ?: nameObjClass[ref.name] } ?: nameObjClass[ref.name]
?: resolveTypeNameClass(ref.name) ?: resolveTypeNameClass(ref.name)
@ -7073,6 +7090,23 @@ class BytecodeCompiler(
} }
} }
private fun resolveClassFromTypeDecl(typeDecl: TypeDecl): ObjClass? {
return when (typeDecl) {
is TypeDecl.Simple -> {
resolveTypeNameClass(typeDecl.name) ?: nameObjClass[typeDecl.name]?.let { cls ->
if (cls == ObjClassType) ObjDynamic.type else cls
}
}
is TypeDecl.Generic -> {
resolveTypeNameClass(typeDecl.name) ?: nameObjClass[typeDecl.name]?.let { cls ->
if (cls == ObjClassType) ObjDynamic.type else cls
}
}
is TypeDecl.Ellipsis -> resolveClassFromTypeDecl(typeDecl.elementType)
else -> null
}
}
private fun isKnownClassReceiver(ref: ObjRef): Boolean { private fun isKnownClassReceiver(ref: ObjRef): Boolean {
return when (ref) { return when (ref) {
is LocalVarRef -> { is LocalVarRef -> {
@ -7274,8 +7308,11 @@ class BytecodeCompiler(
"Bool" -> ObjBool.type "Bool" -> ObjBool.type
"Char" -> ObjChar.type "Char" -> ObjChar.type
"List" -> ObjList.type "List" -> ObjList.type
"ImmutableList" -> ObjImmutableList.type
"Map" -> ObjMap.type "Map" -> ObjMap.type
"ImmutableMap" -> ObjImmutableMap.type
"Set" -> ObjSet.type "Set" -> ObjSet.type
"ImmutableSet" -> ObjImmutableSet.type
"Range", "IntRange" -> ObjRange.type "Range", "IntRange" -> ObjRange.type
"Iterator" -> ObjIterator "Iterator" -> ObjIterator
"Iterable" -> ObjIterable "Iterable" -> ObjIterable
@ -7349,7 +7386,9 @@ class BytecodeCompiler(
"iterator" -> ObjIterator "iterator" -> ObjIterator
"count" -> ObjInt.type "count" -> ObjInt.type
"toSet" -> ObjSet.type "toSet" -> ObjSet.type
"toImmutableSet" -> ObjImmutableSet.type
"toMap" -> ObjMap.type "toMap" -> ObjMap.type
"toImmutableMap" -> ObjImmutableMap.type
"joinToString" -> ObjString.type "joinToString" -> ObjString.type
"now", "now",
"truncateToSecond", "truncateToSecond",
@ -7376,6 +7415,8 @@ class BytecodeCompiler(
"matches" -> ObjBool.type "matches" -> ObjBool.type
"toInt", "toInt",
"toEpochSeconds" -> ObjInt.type "toEpochSeconds" -> ObjInt.type
"toImmutableList" -> ObjImmutableList.type
"toImmutable" -> Obj.rootObjectType
"toMutable" -> ObjMutableBuffer.type "toMutable" -> ObjMutableBuffer.type
"seq" -> ObjFlow.type "seq" -> ObjFlow.type
"encode" -> ObjBitBuffer.type "encode" -> ObjBitBuffer.type

View File

@ -100,6 +100,7 @@ sealed class BytecodeConst {
val isMutable: Boolean, val isMutable: Boolean,
val visibility: Visibility, val visibility: Visibility,
val writeVisibility: Visibility?, val writeVisibility: Visibility?,
val typeDecl: TypeDecl?,
val isTransient: Boolean, val isTransient: Boolean,
val isAbstract: Boolean, val isAbstract: Boolean,
val isClosed: Boolean, val isClosed: Boolean,

View File

@ -348,6 +348,7 @@ class BytecodeStatement private constructor(
stmt.isMutable, stmt.isMutable,
stmt.visibility, stmt.visibility,
stmt.writeVisibility, stmt.writeVisibility,
stmt.typeDecl,
stmt.isAbstract, stmt.isAbstract,
stmt.isClosed, stmt.isClosed,
stmt.isOverride, stmt.isOverride,

View File

@ -2750,6 +2750,7 @@ class CmdDeclClassInstanceField(internal val constId: Int, internal val slot: In
isClosed = decl.isClosed, isClosed = decl.isClosed,
isOverride = decl.isOverride, isOverride = decl.isOverride,
isTransient = decl.isTransient, isTransient = decl.isTransient,
typeDecl = decl.typeDecl,
type = ObjRecord.Type.Field, type = ObjRecord.Type.Field,
fieldId = decl.fieldId fieldId = decl.fieldId
) )

View File

@ -37,8 +37,16 @@ object StdlibDocsBootstrap {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val _list = net.sergeych.lyng.obj.ObjList.type val _list = net.sergeych.lyng.obj.ObjList.type
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val _immutableList = net.sergeych.lyng.obj.ObjImmutableList.type
@Suppress("UNUSED_VARIABLE")
val _map = net.sergeych.lyng.obj.ObjMap.type val _map = net.sergeych.lyng.obj.ObjMap.type
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val _immutableMap = net.sergeych.lyng.obj.ObjImmutableMap.type
@Suppress("UNUSED_VARIABLE")
val _set = net.sergeych.lyng.obj.ObjSet.type
@Suppress("UNUSED_VARIABLE")
val _immutableSet = net.sergeych.lyng.obj.ObjImmutableSet.type
@Suppress("UNUSED_VARIABLE")
val _int = net.sergeych.lyng.obj.ObjInt.type val _int = net.sergeych.lyng.obj.ObjInt.type
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val _real = net.sergeych.lyng.obj.ObjReal.type val _real = net.sergeych.lyng.obj.ObjReal.type

View File

@ -826,6 +826,7 @@ open class ObjClass(
type: ObjRecord.Type = ObjRecord.Type.Field, type: ObjRecord.Type = ObjRecord.Type.Field,
fieldId: Int? = null, fieldId: Int? = null,
methodId: Int? = null, methodId: Int? = null,
typeDecl: net.sergeych.lyng.TypeDecl? = null,
): ObjRecord { ): ObjRecord {
// Validation of override rules: only for non-system declarations // Validation of override rules: only for non-system declarations
var existing: ObjRecord? = null var existing: ObjRecord? = null
@ -921,6 +922,7 @@ open class ObjClass(
isOverride = isOverride, isOverride = isOverride,
isTransient = isTransient, isTransient = isTransient,
type = type, type = type,
typeDecl = typeDecl,
memberName = name, memberName = name,
fieldId = effectiveFieldId, fieldId = effectiveFieldId,
methodId = effectiveMethodId methodId = effectiveMethodId

View File

@ -0,0 +1,197 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
class ObjImmutableList(items: List<Obj> = emptyList()) : Obj() {
private val data: List<Obj> = items.toList()
override val objClass: ObjClass
get() = type
override suspend fun equals(scope: Scope, other: Obj): Boolean {
if (this === other) return true
return when (other) {
is ObjImmutableList -> data.size == other.data.size && data.indices.all { i -> data[i].equals(scope, other.data[i]) }
else -> {
if (other.isInstanceOf(ObjIterable)) compareTo(scope, other) == 0 else false
}
}
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other is ObjImmutableList) {
val mySize = data.size
val otherSize = other.data.size
val commonSize = minOf(mySize, otherSize)
for (i in 0..<commonSize) {
val d = data[i].compareTo(scope, other.data[i])
if (d != 0) return d
}
return mySize.compareTo(otherSize)
}
if (other.isInstanceOf(ObjIterable)) {
val it1 = data.iterator()
val it2 = other.invokeInstanceMethod(scope, "iterator")
val hasNext2 = it2.getInstanceMethod(scope, "hasNext")
val next2 = it2.getInstanceMethod(scope, "next")
while (it1.hasNext()) {
if (!hasNext2.invoke(scope, it2).toBool()) return 1
val d = it1.next().compareTo(scope, next2.invoke(scope, it2))
if (d != 0) return d
}
return if (hasNext2.invoke(scope, it2).toBool()) -1 else 0
}
return -2
}
override suspend fun getAt(scope: Scope, index: Obj): Obj {
return when (index) {
is ObjInt -> data[index.toInt()]
is ObjRange -> {
when {
index.start is ObjInt && index.end is ObjInt -> {
if (index.isEndInclusive)
ObjImmutableList(data.subList(index.start.toInt(), index.end.toInt() + 1))
else
ObjImmutableList(data.subList(index.start.toInt(), index.end.toInt()))
}
index.isOpenStart && !index.isOpenEnd -> {
if (index.isEndInclusive)
ObjImmutableList(data.subList(0, index.end!!.toInt() + 1))
else
ObjImmutableList(data.subList(0, index.end!!.toInt()))
}
index.isOpenEnd && !index.isOpenStart -> ObjImmutableList(data.subList(index.start!!.toInt(), data.size))
index.isOpenStart && index.isOpenEnd -> ObjImmutableList(data)
else -> throw RuntimeException("Can't apply range for index: $index")
}
}
else -> scope.raiseIllegalArgument("Illegal index object for immutable list: ${index.inspect(scope)}")
}
}
override suspend fun contains(scope: Scope, other: Obj): Boolean {
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && other is ObjInt) {
var i = 0
val sz = data.size
while (i < sz) {
val v = data[i]
if (v is ObjInt && v.value == other.value) return true
i++
}
return false
}
return data.contains(other)
}
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
for (item in data) {
if (!callback(item)) break
}
}
override suspend fun plus(scope: Scope, other: Obj): Obj {
return when {
other is ObjImmutableList -> ObjImmutableList(data + other.data)
other is ObjList -> ObjImmutableList(data + other.list)
other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer -> {
val l = other.callMethod<ObjList>(scope, "toList")
ObjImmutableList(data + l.list)
}
else -> ObjImmutableList(data + other)
}
}
override suspend fun minus(scope: Scope, other: Obj): Obj {
if (other !is ObjString && other !is ObjBuffer && other.isInstanceOf(ObjIterable)) {
val toRemove = mutableSetOf<Obj>()
other.enumerate(scope) {
toRemove += it
true
}
return ObjImmutableList(data.filterNot { toRemove.contains(it) })
}
val out = data.toMutableList()
out.remove(other)
return ObjImmutableList(out)
}
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeAnyList(scope, data)
}
override suspend fun lynonType(): LynonType = LynonType.List
override suspend fun defaultToString(scope: Scope): ObjString {
return ObjString(buildString {
append("ImmutableList(")
var first = true
for (v in data) {
if (first) first = false else append(",")
append(v.toString(scope).value)
}
append(")")
})
}
fun toMutableList(): MutableList<Obj> = data.toMutableList()
companion object {
val type = object : ObjClass("ImmutableList", ObjArray) {
override suspend fun callOn(scope: Scope): Obj {
return ObjImmutableList(scope.args.list)
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
return ObjImmutableList(decoder.decodeAnyList(scope))
}
}.apply {
addPropertyDoc(
name = "size",
doc = "Number of elements in this immutable list.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib",
getter = { (this.thisObj as ObjImmutableList).data.size.toObj() }
)
addFnDoc(
name = "toMutable",
doc = "Create a mutable copy of this immutable list.",
returns = type("lyng.List"),
moduleName = "lyng.stdlib"
) {
ObjList(thisAs<ObjImmutableList>().toMutableList())
}
addFnDoc(
name = "toImmutable",
doc = "Return this immutable list.",
returns = type("lyng.ImmutableList"),
moduleName = "lyng.stdlib"
) {
thisObj
}
}
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
class ObjImmutableMap(entries: Map<Obj, Obj> = emptyMap()) : Obj() {
val map: Map<Obj, Obj> = LinkedHashMap(entries)
override val objClass get() = type
override suspend fun equals(scope: Scope, other: Obj): Boolean {
if (this === other) return true
val otherMap = when (other) {
is ObjImmutableMap -> other.map
is ObjMap -> other.map
else -> return false
}
if (map.size != otherMap.size) return false
for ((k, v) in map) {
val ov = other.getAt(scope, k)
if (ov === ObjNull && !other.contains(scope, k)) return false
if (!v.equals(scope, ov)) return false
}
return true
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
val otherMap = when (other) {
is ObjImmutableMap -> other.map
is ObjMap -> other.map
else -> return -1
}
if (map == otherMap) return 0
if (map.size != otherMap.size) return map.size.compareTo(otherMap.size)
return map.toString().compareTo(otherMap.toString())
}
override suspend fun getAt(scope: Scope, index: Obj): Obj = map[index] ?: ObjNull
override suspend fun contains(scope: Scope, other: Obj): Boolean = other in map
override suspend fun defaultToString(scope: Scope): ObjString {
val rendered = buildString {
append("ImmutableMap(")
var first = true
for ((k, v) in map) {
if (!first) append(",")
append(k.inspect(scope))
append(" => ")
append(v.toString(scope).value)
first = false
}
append(")")
}
return ObjString(rendered)
}
override suspend fun lynonType(): LynonType = LynonType.Map
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
val keys = map.keys.map { it.toObj() }
val values = map.values.map { it.toObj() }
encoder.encodeAnyList(scope, keys)
encoder.encodeAnyList(scope, values, fixedSize = true)
}
override suspend fun toJson(scope: Scope): JsonElement {
return JsonObject(map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap())
}
override suspend fun plus(scope: Scope, other: Obj): Obj {
val out = LinkedHashMap(map)
mergeIn(scope, out, other)
return ObjImmutableMap(out)
}
private suspend fun mergeIn(scope: Scope, out: MutableMap<Obj, Obj>, other: Obj) {
when (other) {
is ObjImmutableMap -> out.putAll(other.map)
is ObjMap -> out.putAll(other.map)
is ObjMapEntry -> out[other.key] = other.value
is ObjList -> {
for (e in other.list) {
when (e) {
is ObjMapEntry -> out[e.key] = e.value
else -> {
if (e.isInstanceOf(ObjArray)) {
if (e.invokeInstanceMethod(scope, "size").toInt() != 2)
scope.raiseIllegalArgument("Array element to merge into map must have 2 elements, got $e")
out[e.getAt(scope, 0)] = e.getAt(scope, 1)
} else {
scope.raiseIllegalArgument("map can only be merged with MapEntry elements; got $e")
}
}
}
}
}
else -> scope.raiseIllegalArgument("map can only be merged with Map, ImmutableMap, MapEntry, or List<MapEntry>")
}
}
fun toMutableMapCopy(): MutableMap<Obj, Obj> = LinkedHashMap(map)
companion object {
val type = object : ObjClass("ImmutableMap", ObjCollection) {
override suspend fun callOn(scope: Scope): Obj {
return ObjImmutableMap(ObjMap.listToMap(scope, scope.args.list))
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
val keys = decoder.decodeAnyList(scope)
val values = decoder.decodeAnyList(scope, fixedSize = keys.size)
if (keys.size != values.size) scope.raiseIllegalArgument("map keys and values should be same size")
return ObjImmutableMap(keys.zip(values).toMap())
}
}.apply {
addFnDoc(
name = "getOrNull",
doc = "Get value by key or return null if the key is absent.",
params = listOf(ParamDoc("key")),
returns = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib"
) {
thisAs<ObjImmutableMap>().map[args.firstAndOnly(pos)] ?: ObjNull
}
addPropertyDoc(
name = "size",
doc = "Number of entries in the immutable map.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib",
getter = { thisAs<ObjImmutableMap>().map.size.toObj() }
)
addPropertyDoc(
name = "keys",
doc = "List of keys in this immutable map.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib",
getter = { thisAs<ObjImmutableMap>().map.keys.toObj() }
)
addPropertyDoc(
name = "values",
doc = "List of values in this immutable map.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib",
getter = { ObjList(thisAs<ObjImmutableMap>().map.values.toMutableList()) }
)
addFnDoc(
name = "iterator",
doc = "Iterator over map entries as MapEntry objects.",
moduleName = "lyng.stdlib"
) {
ObjKotlinIterator(thisAs<ObjImmutableMap>().map.entries.iterator())
}
addFnDoc(
name = "toMutable",
doc = "Create a mutable copy of this immutable map.",
returns = type("lyng.Map"),
moduleName = "lyng.stdlib"
) {
ObjMap(thisAs<ObjImmutableMap>().toMutableMapCopy())
}
addFnDoc(
name = "toImmutable",
doc = "Return this immutable map.",
returns = type("lyng.ImmutableMap"),
moduleName = "lyng.stdlib"
) { thisObj }
}
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
class ObjImmutableSet(items: Collection<Obj> = emptyList()) : Obj() {
private val data: Set<Obj> = LinkedHashSet(items)
override val objClass get() = type
override suspend fun equals(scope: Scope, other: Obj): Boolean {
if (this === other) return true
val otherSet = when (other) {
is ObjImmutableSet -> other.data
is ObjSet -> other.set
else -> return false
}
if (data.size != otherSet.size) return false
for (e in data) {
if (!other.contains(scope, e)) return false
}
return true
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
val otherSet = when (other) {
is ObjImmutableSet -> other.data
is ObjSet -> other.set
else -> return -2
}
if (data == otherSet) return 0
if (data.size != otherSet.size) return data.size.compareTo(otherSet.size)
return data.toString().compareTo(otherSet.toString())
}
override suspend fun contains(scope: Scope, other: Obj): Boolean = data.contains(other)
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
for (item in data) {
if (!callback(item)) break
}
}
override suspend fun plus(scope: Scope, other: Obj): Obj {
val merged = LinkedHashSet(data)
when {
other is ObjImmutableSet -> merged.addAll(other.data)
other is ObjSet -> merged.addAll(other.set)
other is ObjString || other is ObjBuffer || !other.isInstanceOf(ObjIterable) -> merged.add(other)
else -> other.enumerate(scope) { merged += it; true }
}
return ObjImmutableSet(merged)
}
override suspend fun minus(scope: Scope, other: Obj): Obj {
val out = LinkedHashSet(data)
when {
other is ObjImmutableSet -> out.removeAll(other.data)
other is ObjSet -> out.removeAll(other.set)
other is ObjString || other is ObjBuffer || !other.isInstanceOf(ObjIterable) -> out.remove(other)
else -> other.enumerate(scope) { out.remove(it); true }
}
return ObjImmutableSet(out)
}
override suspend fun mul(scope: Scope, other: Obj): Obj {
val right = when (other) {
is ObjImmutableSet -> other.data
is ObjSet -> other.set
else -> scope.raiseIllegalArgument("set operator * requires another set")
}
return ObjImmutableSet(data.intersect(right))
}
override suspend fun lynonType(): LynonType = LynonType.Set
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeAnyList(scope, data.toList())
}
fun toMutableSet(): MutableSet<Obj> = LinkedHashSet(data)
companion object {
val type: ObjClass = object : ObjClass("ImmutableSet", ObjCollection) {
override suspend fun callOn(scope: Scope): Obj {
return ObjImmutableSet(scope.args.list)
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjImmutableSet(decoder.decodeAnyList(scope))
}.apply {
addFnDoc(
name = "size",
doc = "Number of elements in this immutable set.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjImmutableSet>().data.size.toObj()
}
addFnDoc(
name = "intersect",
doc = "Intersection with another set. Returns a new immutable set.",
params = listOf(ParamDoc("other")),
returns = type("lyng.ImmutableSet"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjImmutableSet>().mul(requireScope(), args.firstAndOnly())
}
addFnDoc(
name = "iterator",
doc = "Iterator over elements of this immutable set.",
moduleName = "lyng.stdlib"
) {
ObjKotlinObjIterator(thisAs<ObjImmutableSet>().data.iterator())
}
addFnDoc(
name = "union",
doc = "Union with another set or iterable. Returns a new immutable set.",
params = listOf(ParamDoc("other")),
returns = type("lyng.ImmutableSet"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjImmutableSet>().plus(requireScope(), args.firstAndOnly())
}
addFnDoc(
name = "subtract",
doc = "Subtract another set or iterable from this set. Returns a new immutable set.",
params = listOf(ParamDoc("other")),
returns = type("lyng.ImmutableSet"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjImmutableSet>().minus(requireScope(), args.firstAndOnly())
}
addFnDoc(
name = "toMutable",
doc = "Create a mutable copy of this immutable set.",
returns = type("lyng.Set"),
moduleName = "lyng.stdlib"
) {
ObjSet(thisAs<ObjImmutableSet>().toMutableSet())
}
addFnDoc(
name = "toImmutable",
doc = "Return this immutable set.",
returns = type("lyng.ImmutableSet"),
moduleName = "lyng.stdlib"
) { thisObj }
}
}
}

View File

@ -50,6 +50,21 @@ val ObjIterable by lazy {
ObjList(result) ObjList(result)
} }
addFnDoc(
name = "toImmutableList",
doc = "Collect elements of this iterable into a new immutable list.",
returns = type("lyng.ImmutableList"),
moduleName = "lyng.stdlib"
) {
val scope = requireScope()
val result = mutableListOf<Obj>()
val it = thisObj.invokeInstanceMethod(scope, "iterator")
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(scope, "next"))
}
ObjImmutableList(result)
}
// it is not effective, but it is open: // it is not effective, but it is open:
addFnDoc( addFnDoc(
name = "contains", name = "contains",
@ -109,6 +124,28 @@ val ObjIterable by lazy {
} }
) )
addPropertyDoc(
name = "toImmutableSet",
doc = "Collect elements of this iterable into a new immutable set.",
type = type("lyng.ImmutableSet"),
moduleName = "lyng.stdlib",
getter = {
when (val self = this.thisObj) {
is ObjImmutableSet -> self
is ObjSet -> ObjImmutableSet(self.set)
else -> {
val result = mutableSetOf<Obj>()
val scope = requireScope()
val it = self.invokeInstanceMethod(scope, "iterator")
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(scope, "next"))
}
ObjImmutableSet(result)
}
}
}
)
addPropertyDoc( addPropertyDoc(
name = "toMap", name = "toMap",
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.", doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
@ -128,6 +165,25 @@ val ObjIterable by lazy {
} }
) )
addPropertyDoc(
name = "toImmutableMap",
doc = "Collect pairs into an immutable map using [0] as key and [1] as value for each element.",
type = type("lyng.ImmutableMap"),
moduleName = "lyng.stdlib",
getter = {
val result = linkedMapOf<Obj, Obj>()
val scope = requireScope()
this.thisObj.enumerate(scope) { pair ->
when (pair) {
is ObjMapEntry -> result[pair.key] = pair.value
else -> result[pair.getAt(scope, 0)] = pair.getAt(scope, 1)
}
true
}
ObjImmutableMap(result)
}
)
addFnDoc( addFnDoc(
name = "associateBy", name = "associateBy",
doc = "Build a map from elements using the lambda result as key.", doc = "Build a map from elements using the lambda result as key.",

View File

@ -30,6 +30,15 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() { class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
private fun shouldTreatAsSingleElement(scope: Scope, other: Obj): Boolean {
if (!other.isInstanceOf(ObjIterable)) return true
val declaredElementType = scope.declaredListElementTypeForValue(this)
if (declaredElementType != null && matchesTypeDecl(scope, other, declaredElementType)) {
return true
}
if (other is ObjString || other is ObjBuffer) return true
return false
}
override suspend fun equals(scope: Scope, other: Obj): Boolean { override suspend fun equals(scope: Scope, other: Obj): Boolean {
if (this === other) return true if (this === other) return true
@ -127,7 +136,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
other is ObjList -> other is ObjList ->
ObjList((list + other.list).toMutableList()) ObjList((list + other.list).toMutableList())
other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer -> { !shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable) -> {
val l = other.callMethod<ObjList>(scope, "toList") val l = other.callMethod<ObjList>(scope, "toList")
ObjList((list + l.list).toMutableList()) ObjList((list + l.list).toMutableList())
} }
@ -143,7 +152,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun plusAssign(scope: Scope, other: Obj): Obj { override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
if (other is ObjList) { if (other is ObjList) {
list.addAll(other.list) list.addAll(other.list)
} else if (other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer) { } else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
list.addAll(otherList) list.addAll(otherList)
} else { } else {
@ -152,6 +161,43 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return this return this
} }
override suspend fun minus(scope: Scope, other: Obj): Obj {
val out = list.toMutableList()
if (shouldTreatAsSingleElement(scope, other)) {
out.remove(other)
return ObjList(out)
}
if (other.isInstanceOf(ObjIterable)) {
val toRemove = mutableSetOf<Obj>()
other.enumerate(scope) {
toRemove += it
true
}
out.removeAll { toRemove.contains(it) }
return ObjList(out)
}
out.remove(other)
return ObjList(out)
}
override suspend fun minusAssign(scope: Scope, other: Obj): Obj {
if (shouldTreatAsSingleElement(scope, other)) {
list.remove(other)
return this
}
if (other.isInstanceOf(ObjIterable)) {
val toRemove = mutableSetOf<Obj>()
other.enumerate(scope) {
toRemove += it
true
}
list.removeAll { toRemove.contains(it) }
return this
}
list.remove(other)
return this
}
override suspend fun contains(scope: Scope, other: Obj): Boolean { override suspend fun contains(scope: Scope, other: Obj): Boolean {
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
// Fast path: int membership in a list of ints (common case in benches) // Fast path: int membership in a list of ints (common case in benches)
@ -516,6 +562,14 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
ObjInt((-1).toLong()) ObjInt((-1).toLong())
} }
addFnDoc(
name = "toImmutable",
doc = "Create an immutable snapshot of this list.",
returns = type("lyng.ImmutableList"),
moduleName = "lyng.stdlib"
) {
ObjImmutableList(thisAs<ObjList>().list)
}
} }
} }
} }

View File

@ -107,8 +107,12 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
override suspend fun equals(scope: Scope, other: Obj): Boolean { override suspend fun equals(scope: Scope, other: Obj): Boolean {
if (this === other) return true if (this === other) return true
if (other !is ObjMap) return false val otherSize = when (other) {
if (map.size != other.map.size) return false is ObjMap -> other.map.size
is ObjImmutableMap -> other.map.size
else -> return false
}
if (map.size != otherSize) return false
for ((k, v) in map) { for ((k, v) in map) {
val otherV = other.getAt(scope, k) val otherV = other.getAt(scope, k)
if (otherV === ObjNull && !other.contains(scope, k)) return false if (otherV === ObjNull && !other.contains(scope, k)) return false
@ -131,14 +135,16 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
} }
override suspend fun compareTo(scope: Scope, other: Obj): Int { override suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other is ObjMap) { val otherMap = when (other) {
if (map == other.map) return 0 is ObjMap -> other.map
if (map.size != other.map.size) return map.size.compareTo(other.map.size) is ObjImmutableMap -> other.map
// for same size, if they are not equal, we don't have a stable order else -> return -1
// but let's try to be consistent
return map.toString().compareTo(other.map.toString())
} }
return -1 if (map == otherMap) return 0
if (map.size != otherMap.size) return map.size.compareTo(otherMap.size)
// for same size, if they are not equal, we don't have a stable order
// but let's try to be consistent
return map.toString().compareTo(otherMap.toString())
} }
override suspend fun defaultToString(scope: Scope): ObjString { override suspend fun defaultToString(scope: Scope): ObjString {
@ -311,6 +317,14 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
) { ) {
ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator()) ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
} }
addFnDoc(
name = "toImmutable",
doc = "Create an immutable snapshot of this map.",
returns = type("lyng.ImmutableMap"),
moduleName = "lyng.stdlib"
) {
ObjImmutableMap(thisAs<ObjMap>().map)
}
} }
} }
@ -334,6 +348,11 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
map[k] = v map[k] = v
} }
} }
is ObjImmutableMap -> {
for ((k, v) in other.map) {
map[k] = v
}
}
is ObjMapEntry -> { is ObjMapEntry -> {
map[other.key] = other.value map[other.key] = other.value
} }

View File

@ -27,11 +27,25 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() { class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
private fun shouldTreatAsSingleElement(scope: Scope, other: Obj): Boolean {
if (!other.isInstanceOf(ObjIterable)) return true
val declaredElementType = scope.declaredSetElementTypeForValue(this)
if (declaredElementType != null && matchesTypeDecl(scope, other, declaredElementType)) {
return true
}
// Strings and buffers are iterable but usually expected to be atomic values for set +/- operators.
if (other is ObjString || other is ObjBuffer) return true
return false
}
override suspend fun equals(scope: Scope, other: Obj): Boolean { override suspend fun equals(scope: Scope, other: Obj): Boolean {
if (this === other) return true if (this === other) return true
if (other !is ObjSet) return false val otherSet = when (other) {
if (set.size != other.set.size) return false is ObjSet -> other.set
is ObjImmutableSet -> other.toMutableSet()
else -> return false
}
if (set.size != otherSet.size) return false
// Sets are equal if all my elements are in other and vice versa // Sets are equal if all my elements are in other and vice versa
// contains() in ObjSet uses equals(scope, ...), so we need to be careful // contains() in ObjSet uses equals(scope, ...), so we need to be careful
for (e in set) { for (e in set) {
@ -53,6 +67,9 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
} }
override suspend fun plus(scope: Scope, other: Obj): Obj { override suspend fun plus(scope: Scope, other: Obj): Obj {
if (shouldTreatAsSingleElement(scope, other)) {
return ObjSet((set + other).toMutableSet())
}
return ObjSet( return ObjSet(
if (other is ObjSet) if (other is ObjSet)
(set + other.set).toMutableSet() (set + other.set).toMutableSet()
@ -73,6 +90,10 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
} }
override suspend fun plusAssign(scope: Scope, other: Obj): Obj { override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
if (shouldTreatAsSingleElement(scope, other)) {
set += other
return this
}
when (other) { when (other) {
is ObjSet -> { is ObjSet -> {
set += other.set set += other.set
@ -98,13 +119,17 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
} }
override suspend fun mul(scope: Scope, other: Obj): Obj { override suspend fun mul(scope: Scope, other: Obj): Obj {
return if (other is ObjSet) { return when (other) {
ObjSet(set.intersect(other.set).toMutableSet()) is ObjSet -> ObjSet(set.intersect(other.set).toMutableSet())
} else is ObjImmutableSet -> ObjSet(set.intersect(other.toMutableSet()).toMutableSet())
scope.raiseIllegalArgument("set operator * requires another set") else -> scope.raiseIllegalArgument("set operator * requires another set")
}
} }
override suspend fun minus(scope: Scope, other: Obj): Obj { override suspend fun minus(scope: Scope, other: Obj): Obj {
if (shouldTreatAsSingleElement(scope, other)) {
return ObjSet((set - other).toMutableSet())
}
return when { return when {
other is ObjSet -> ObjSet(set.minus(other.set).toMutableSet()) other is ObjSet -> ObjSet(set.minus(other.set).toMutableSet())
other.isInstanceOf(ObjIterable) -> { other.isInstanceOf(ObjIterable) -> {
@ -115,8 +140,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
} }
ObjSet((set - otherSet).toMutableSet()) ObjSet((set - otherSet).toMutableSet())
} }
else -> else -> ObjSet((set - other).toMutableSet())
scope.raiseIllegalArgument("set operator - requires another set or Iterable")
} }
} }
@ -125,12 +149,14 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
} }
override suspend fun compareTo(scope: Scope, other: Obj): Int { override suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other is ObjSet) { val otherSet = when (other) {
if (set == other.set) return 0 is ObjSet -> other.set
if (set.size != other.set.size) return set.size.compareTo(other.set.size) is ObjImmutableSet -> other.toMutableSet()
return set.toString().compareTo(other.set.toString()) else -> return -2
} }
return -2 if (set == otherSet) return 0
if (set.size != otherSet.size) return set.size.compareTo(otherSet.size)
return set.toString().compareTo(otherSet.toString())
} }
override fun hashCode(): Int { override fun hashCode(): Int {
@ -214,6 +240,14 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
for( x in args.list ) set -= x for( x in args.list ) set -= x
if( n == set.size ) ObjFalse else ObjTrue if( n == set.size ) ObjFalse else ObjTrue
} }
addFnDoc(
name = "toImmutable",
doc = "Create an immutable snapshot of this set.",
returns = type("lyng.ImmutableSet"),
moduleName = "lyng.stdlib"
) {
ObjImmutableSet(thisAs<ObjSet>().set)
}
} }
} }
} }

View File

@ -0,0 +1,107 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Test
import kotlin.test.assertFails
class ImmutableCollectionsTest {
@Test
fun immutableListSnapshotAndConversion() = runTest {
eval(
"""
val src = [1,2,3]
val imm = src.toImmutable()
assert(imm is ImmutableList<Int>)
assert(imm is Array<Int>)
assert(imm is Collection<Int>)
assert(imm is Iterable<Int>)
src += 4
assertEquals(3, imm.size)
assertEquals([1,2,3], imm.toMutable())
assertEquals([1,2,3], (1..3).toImmutableList().toMutable())
"""
)
}
@Test
fun immutableSetSnapshotAndConversion() = runTest {
eval(
"""
val src = Set(1,2,3)
val imm = src.toImmutable()
assert(imm is ImmutableSet<Int>)
assert(imm is Collection<Int>)
assert(imm is Iterable<Int>)
src += 4
assertEquals(3, imm.size)
assertEquals(Set(1,2,3), imm.toMutable())
assertEquals(Set(1,2,3), [1,2,3].toImmutableSet.toMutable())
"""
)
}
@Test
fun immutableMapSnapshotAndConversion() = runTest {
eval(
"""
val src = Map("a" => 1, "b" => 2)
val imm = src.toImmutable()
assert(imm is ImmutableMap<String,Int>)
assert(imm is Collection<MapEntry<String,Int>>)
assert(imm is Iterable<MapEntry<String,Int>>)
src["a"] = 100
assertEquals(1, imm["a"])
assertEquals(Map("a" => 1, "b" => 2), imm.toMutable())
val imm2 = ["x" => 10, "y" => 20].toImmutableMap
assertEquals(10, imm2["x"])
assertEquals(Map("x" => 10, "y" => 20), imm2.toMutable())
"""
)
}
@Test
fun immutableCollectionsRejectMutationOps() = runTest {
assertFails {
eval(
"""
val xs = ImmutableList(1,2,3)
xs += 4
"""
)
}
assertFails {
eval(
"""
val s = ImmutableSet(1,2,3)
s += 4
"""
)
}
assertFails {
eval(
"""
val m = ImmutableMap("a" => 1)
m["a"] = 10
"""
)
}
}
}

View File

@ -20,6 +20,7 @@ import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.eval import net.sergeych.lyng.eval
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -187,10 +188,12 @@ class TypesTest {
eval( eval(
""" """
class Ctx { class Ctx {
val contract = null
fun println(msg: String) = msg fun println(msg: String) = msg
} }
fun use(callContext) { fun use(callContext) {
assert(callContext is Ctx) assert(callContext is Ctx)
assert(callContext.contract == null)
callContext.println("hello") callContext.println("hello")
} }
assertEquals("hello", use(Ctx())) assertEquals("hello", use(Ctx()))
@ -397,4 +400,114 @@ class TypesTest {
fun testOk5() { l4(1, "a", "b", "x") } fun testOk5() { l4(1, "a", "b", "x") }
""".trimIndent()) """.trimIndent())
} }
@Test
fun testSetTyped() = runTest {
eval("""
var s = Set<String>()
val typed: Set<String> = s
assertEquals(Set(), typed)
s += "foo"
assertEquals(Set("foo"), s)
s -= "foo"
assertEquals(Set(), s)
s += ["foo", "bar"]
assertEquals(Set("foo", "bar"), s)
""".trimIndent())
}
@Test
fun testListTyped() = runTest {
eval("""
var l = List<String>()
val typed: List<String> = l
assertEquals(List(), typed)
l += "foo"
assertEquals(List("foo"), l)
l -= "foo"
assertEquals(List(), l)
l += ["foo", "bar"]
assertEquals(List("foo", "bar"), l)
""".trimIndent())
}
@Test
fun testAliasesInGenerics1() = runTest {
val scope = Script.newScope()
scope.eval("""
type IntList<T: Int> = List<T>
type IntMap<K,V> = Map<K,V>
type IntSet<T: Int> = Set<T>
type IntPair<T: Int> = Pair<T,T>
type IntTriple<T: Int> = Triple<T,T,T>
type IntQuad<T: Int> = Quad<T,T,T,T>
import lyng.buffer
type Tag = String | Buffer
class X {
var tags: Set<Tag> = Set()
}
val x = X()
x.tags += "tag1"
assertEquals(Set("tag1"), x.tags)
x.tags += "tag2"
assertEquals(Set("tag1", "tag2"), x.tags)
x.tags += Buffer("tag3")
assertEquals(Set("tag1", "tag2", Buffer("tag3")), x.tags)
x.tags += Buffer("tag4")
assertEquals(Set("tag1", "tag2", Buffer("tag3"), Buffer("tag4")), x.tags)
""")
scope.eval("""
assert(x is X)
x.tags += "42"
assertEquals(Set("tag1", "tag2", Buffer("tag3"), Buffer("tag4"), "42"), x.tags)
""".trimIndent())
// now this must fail becaise element type does not match the declared:
assertFailsWith<ScriptError> {
scope.eval(
"""
x.tags += 42
""".trimIndent()
)
}
}
@Test
fun testAliasesInGenericsList1() = runTest {
val scope = Script.newScope()
scope.eval("""
import lyng.buffer
type Tag = String | Buffer
class X {
var tags: List<Tag> = List()
}
val x = X()
x.tags += "tag1"
assertEquals(List("tag1"), x.tags)
x.tags += "tag2"
assertEquals(List("tag1", "tag2"), x.tags)
x.tags += Buffer("tag3")
assertEquals(List("tag1", "tag2", Buffer("tag3")), x.tags)
x.tags += ["tag4", Buffer("tag5")]
assertEquals(List("tag1", "tag2", Buffer("tag3"), "tag4", Buffer("tag5")), x.tags)
""")
scope.eval("""
assert(x is X)
x.tags += "42"
assertEquals(List("tag1", "tag2", Buffer("tag3"), "tag4", Buffer("tag5"), "42"), x.tags)
""".trimIndent())
assertFailsWith<ScriptError> {
scope.eval(
"""
x.tags += 42
""".trimIndent()
)
}
}
} }

View File

@ -12,6 +12,11 @@ extern class Iterable<T> {
fun forEach(action: (T)->Void): Void fun forEach(action: (T)->Void): Void
fun map<R>(transform: (T)->R): List<R> fun map<R>(transform: (T)->R): List<R>
fun toList(): List<T> fun toList(): List<T>
fun toImmutableList(): ImmutableList<T>
val toSet: Set<T>
val toImmutableSet: ImmutableSet<T>
val toMap: Map<Object,Object>
val toImmutableMap: ImmutableMap<Object,Object>
} }
extern class Iterator<T> { extern class Iterator<T> {
@ -28,13 +33,19 @@ class KotlinIterator<T> : Iterator<T> {
} }
extern class Collection<T> : Iterable<T> { extern class Collection<T> : Iterable<T> {
val size: Int
} }
extern class Array<T> : Collection<T> { extern class Array<T> : Collection<T> {
} }
extern class ImmutableList<T> : Array<T> {
fun toMutable(): List<T>
}
extern class List<T> : Array<T> { extern class List<T> : Array<T> {
fun add(value: T, more...): Void fun add(value: T, more...): Void
fun toImmutable(): ImmutableList<T>
} }
extern class RingBuffer<T> : Iterable<T> { extern class RingBuffer<T> : Iterable<T> {
@ -44,12 +55,26 @@ extern class RingBuffer<T> : Iterable<T> {
} }
extern class Set<T> : Collection<T> { extern class Set<T> : Collection<T> {
fun toImmutable(): ImmutableSet<T>
} }
extern class Map<K,V> { extern class ImmutableSet<T> : Collection<T> {
fun toMutable(): Set<T>
} }
extern class MapEntry<K,V> extern class Map<K,V> : Collection<MapEntry<K,V>> {
fun toImmutable(): ImmutableMap<K,V>
}
extern class ImmutableMap<K,V> : Collection<MapEntry<K,V>> {
fun getOrNull(key: K): V?
fun toMutable(): Map<K,V>
}
extern class MapEntry<K,V> : Array<Object> {
val key: K
val value: V
}
// Built-in math helpers (implemented in host runtime). // Built-in math helpers (implemented in host runtime).
extern fun abs(x: Object): Real extern fun abs(x: Object): Real