Compare commits
No commits in common. "83099148bd9cb524558643a2740940f6ba12753d" and "671583638ba95648f8d6a3ff7210f19eace75c5d" have entirely different histories.
83099148bd
...
671583638b
@ -9,7 +9,6 @@ For a programmer-focused migration summary across 1.5.x, see `docs/whats_new_1_5
|
|||||||
- `1.5.4` is the stabilization release for the 1.5 feature set.
|
- `1.5.4` is the stabilization release for the 1.5 feature set.
|
||||||
- The 1.5 line now brings together richer ranges and loops, interpolation, math modules, immutable and observable collections, richer `lyngio`, and much better CLI/IDE support.
|
- The 1.5 line now brings together richer ranges and loops, interpolation, math modules, immutable and observable collections, richer `lyngio`, and much better CLI/IDE support.
|
||||||
- `1.5.4` specifically fixes user-visible issues around decimal arithmetic, mixed numeric flows, list behavior, and observable list hooks.
|
- `1.5.4` specifically fixes user-visible issues around decimal arithmetic, mixed numeric flows, list behavior, and observable list hooks.
|
||||||
- `1.5.4` also fixes extension-member registration for named singleton `object` declarations, so `fun X.foo()` and `val X.bar` now work as expected.
|
|
||||||
- The docs, homepage samples, and release metadata now point at the current stable version.
|
- The docs, homepage samples, and release metadata now point at the current stable version.
|
||||||
|
|
||||||
## User Highlights Across 1.5.x
|
## User Highlights Across 1.5.x
|
||||||
@ -304,23 +303,6 @@ object Config {
|
|||||||
Config.show()
|
Config.show()
|
||||||
```
|
```
|
||||||
|
|
||||||
Named singleton objects can also be used as extension receivers:
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
object X {
|
|
||||||
fun base() = "base"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun X.decorate(value): String {
|
|
||||||
this.base() + ":" + value.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
val X.tag get() = this.base() + ":tag"
|
|
||||||
|
|
||||||
assertEquals("base:42", X.decorate(42))
|
|
||||||
assertEquals("base:tag", X.tag)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nested Declarations and Lifted Enums
|
### Nested Declarations and Lifted Enums
|
||||||
You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier.
|
You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier.
|
||||||
|
|
||||||
|
|||||||
@ -1449,13 +1449,6 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
cc.nextNonWhitespace()
|
cc.nextNonWhitespace()
|
||||||
val afterSegment = cc.peekNextNonWhitespace()
|
val afterSegment = cc.peekNextNonWhitespace()
|
||||||
if (afterSegment.type == Token.Type.LT) {
|
|
||||||
val nextAfterSuffix = peekTokenAfterExtensionReceiverSegmentSuffix()
|
|
||||||
if (nextAfterSuffix != Token.Type.DOT) {
|
|
||||||
cc.restorePos(dotPos)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (afterSegment.type != Token.Type.DOT &&
|
if (afterSegment.type != Token.Type.DOT &&
|
||||||
afterSegment.type != Token.Type.LT &&
|
afterSegment.type != Token.Type.LT &&
|
||||||
afterSegment.type != Token.Type.QUESTION &&
|
afterSegment.type != Token.Type.QUESTION &&
|
||||||
@ -1503,38 +1496,6 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun peekTokenAfterExtensionReceiverSegmentSuffix(): Token.Type {
|
|
||||||
val saved = cc.savePos()
|
|
||||||
try {
|
|
||||||
if (cc.peekNextNonWhitespace().type == Token.Type.LT) {
|
|
||||||
var depth = 0
|
|
||||||
while (true) {
|
|
||||||
when (val tok = cc.nextNonWhitespace().type) {
|
|
||||||
Token.Type.LT -> depth += 1
|
|
||||||
Token.Type.GT -> {
|
|
||||||
depth -= 1
|
|
||||||
if (depth <= 0) break
|
|
||||||
}
|
|
||||||
Token.Type.SHR -> {
|
|
||||||
depth -= 2
|
|
||||||
if (depth <= 0) break
|
|
||||||
}
|
|
||||||
Token.Type.EOF -> return Token.Type.EOF
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cc.peekNextNonWhitespace().type == Token.Type.QUESTION ||
|
|
||||||
cc.peekNextNonWhitespace().type == Token.Type.IFNULLASSIGN
|
|
||||||
) {
|
|
||||||
cc.nextNonWhitespace()
|
|
||||||
}
|
|
||||||
return cc.peekNextNonWhitespace().type
|
|
||||||
} finally {
|
|
||||||
cc.restorePos(saved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldImplicitTypeVar(name: String, explicit: Set<String>): Boolean {
|
private fun shouldImplicitTypeVar(name: String, explicit: Set<String>): Boolean {
|
||||||
if (explicit.contains(name)) return true
|
if (explicit.contains(name)) return true
|
||||||
if (name.contains('.')) return false
|
if (name.contains('.')) return false
|
||||||
@ -1836,8 +1797,7 @@ class Compiler(
|
|||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = importBindings.keys,
|
preparedModuleBindingNames = importBindings.keys,
|
||||||
scopeRefPosByName = moduleReferencePosByName,
|
scopeRefPosByName = moduleReferencePosByName,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef,
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
implicitThisTypeName = currentImplicitThisTypeName()
|
|
||||||
) as BytecodeStatement
|
) as BytecodeStatement
|
||||||
unwrapped to bytecodeStmt.bytecodeFunction()
|
unwrapped to bytecodeStmt.bytecodeFunction()
|
||||||
} else {
|
} else {
|
||||||
@ -2258,8 +2218,7 @@ class Compiler(
|
|||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = importBindings.keys,
|
preparedModuleBindingNames = importBindings.keys,
|
||||||
scopeRefPosByName = moduleReferencePosByName,
|
scopeRefPosByName = moduleReferencePosByName,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef,
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
implicitThisTypeName = currentImplicitThisTypeName()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4294,13 +4253,6 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
cc.nextNonWhitespace()
|
cc.nextNonWhitespace()
|
||||||
val afterSegment = cc.peekNextNonWhitespace()
|
val afterSegment = cc.peekNextNonWhitespace()
|
||||||
if (afterSegment.type == Token.Type.LT) {
|
|
||||||
val nextAfterSuffix = peekTokenAfterExtensionReceiverSegmentSuffix()
|
|
||||||
if (nextAfterSuffix != Token.Type.DOT) {
|
|
||||||
cc.restorePos(dotPos)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (afterSegment.type != Token.Type.DOT &&
|
if (afterSegment.type != Token.Type.DOT &&
|
||||||
afterSegment.type != Token.Type.LT &&
|
afterSegment.type != Token.Type.LT &&
|
||||||
afterSegment.type != Token.Type.QUESTION &&
|
afterSegment.type != Token.Type.QUESTION &&
|
||||||
|
|||||||
@ -85,7 +85,8 @@ internal suspend fun executeFunctionDecl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spec.extTypeName != null) {
|
if (spec.extTypeName != null) {
|
||||||
val type = scope.resolveExtensionReceiverClass(spec.extTypeName)
|
val type = scope[spec.extTypeName]?.value ?: scope.raiseSymbolNotFound("class ${spec.extTypeName} not found")
|
||||||
|
if (type !is ObjClass) scope.raiseClassCastError("${spec.extTypeName} is not the class instance")
|
||||||
scope.addExtension(
|
scope.addExtension(
|
||||||
type,
|
type,
|
||||||
spec.name,
|
spec.name,
|
||||||
@ -166,7 +167,8 @@ internal suspend fun executeFunctionDecl(
|
|||||||
val compiledFnBody = annotatedFnBody
|
val compiledFnBody = annotatedFnBody
|
||||||
|
|
||||||
spec.extTypeName?.let { typeName ->
|
spec.extTypeName?.let { typeName ->
|
||||||
val type = scope.resolveExtensionReceiverClass(typeName)
|
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
|
||||||
|
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
|
||||||
if (spec.isStatic) {
|
if (spec.isStatic) {
|
||||||
type.createClassField(
|
type.createClassField(
|
||||||
spec.name,
|
spec.name,
|
||||||
|
|||||||
@ -1004,13 +1004,4 @@ open class Scope(
|
|||||||
return rec.value as? net.sergeych.lyng.obj.ObjClass
|
return rec.value as? net.sergeych.lyng.obj.ObjClass
|
||||||
?: raiseClassCastError("Expected class $name, got ${rec.value.objClass.className}")
|
?: raiseClassCastError("Expected class $name, got ${rec.value.objClass.className}")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun resolveExtensionReceiverClass(name: String): net.sergeych.lyng.obj.ObjClass {
|
|
||||||
val value = get(name)?.value ?: raiseSymbolNotFound("class $name not found")
|
|
||||||
return when (value) {
|
|
||||||
is net.sergeych.lyng.obj.ObjClass -> value
|
|
||||||
is net.sergeych.lyng.obj.ObjInstance -> value.objClass
|
|
||||||
else -> raiseClassCastError("$name is not the class instance")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,6 @@ class BytecodeCompiler(
|
|||||||
private val preparedModuleBindingNames: Set<String> = emptySet(),
|
private val preparedModuleBindingNames: Set<String> = emptySet(),
|
||||||
private val scopeRefPosByName: Map<String, Pos> = emptyMap(),
|
private val scopeRefPosByName: Map<String, Pos> = emptyMap(),
|
||||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
private val implicitThisTypeName: String? = null,
|
|
||||||
) {
|
) {
|
||||||
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
||||||
private var builder = CmdBuilder()
|
private var builder = CmdBuilder()
|
||||||
@ -695,8 +694,6 @@ class BytecodeCompiler(
|
|||||||
val receiver = ref.preferredThisTypeName()?.let { typeName ->
|
val receiver = ref.preferredThisTypeName()?.let { typeName ->
|
||||||
compileThisVariantRef(typeName) ?: return null
|
compileThisVariantRef(typeName) ?: return null
|
||||||
} ?: compileThisRef()
|
} ?: compileThisRef()
|
||||||
val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) }
|
|
||||||
?: implicitThisTypeName?.let { resolveTypeNameClass(it) }
|
|
||||||
val fieldId = ref.fieldId ?: -1
|
val fieldId = ref.fieldId ?: -1
|
||||||
val methodId = ref.methodId ?: -1
|
val methodId = ref.methodId ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
@ -713,13 +710,11 @@ class BytecodeCompiler(
|
|||||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||||
builder.emit(Opcode.CALL_SLOT, calleeObj.slot, args.base, encodedCount, dst)
|
builder.emit(Opcode.CALL_SLOT, calleeObj.slot, args.base, encodedCount, dst)
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
annotateIndexedReceiverSlot(dst, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
|
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, slot)
|
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, slot)
|
||||||
updateSlotType(slot, SlotType.OBJ)
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
annotateIndexedReceiverSlot(slot, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
|
|
||||||
CompiledValue(slot, SlotType.OBJ)
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
is ImplicitThisMethodCallRef -> compileImplicitThisMethodCall(ref)
|
is ImplicitThisMethodCallRef -> compileImplicitThisMethodCall(ref)
|
||||||
@ -1428,23 +1423,6 @@ class BytecodeCompiler(
|
|||||||
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
||||||
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
||||||
)
|
)
|
||||||
val intOnlyOps = setOf(BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR)
|
|
||||||
if (op in intOnlyOps) {
|
|
||||||
coerceToArithmeticInt(leftRef, a)?.let { a = it }
|
|
||||||
coerceToArithmeticInt(rightRef, b)?.let { b = it }
|
|
||||||
if (a.type == SlotType.OBJ) {
|
|
||||||
val intSlot = allocSlot()
|
|
||||||
builder.emit(Opcode.UNBOX_INT_OBJ, emitAssertObjSlotIsInt(a.slot), intSlot)
|
|
||||||
updateSlotType(intSlot, SlotType.INT)
|
|
||||||
a = CompiledValue(intSlot, SlotType.INT)
|
|
||||||
}
|
|
||||||
if (b.type == SlotType.OBJ) {
|
|
||||||
val intSlot = allocSlot()
|
|
||||||
builder.emit(Opcode.UNBOX_INT_OBJ, emitAssertObjSlotIsInt(b.slot), intSlot)
|
|
||||||
updateSlotType(intSlot, SlotType.INT)
|
|
||||||
b = CompiledValue(intSlot, SlotType.INT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val leftIsLoopVar = (leftRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
|
val leftIsLoopVar = (leftRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
|
||||||
val rightIsLoopVar = (rightRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
|
val rightIsLoopVar = (rightRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
|
||||||
if (a.type == SlotType.UNKNOWN && b.type == SlotType.INT && op in intOps && leftIsLoopVar) {
|
if (a.type == SlotType.UNKNOWN && b.type == SlotType.INT && op in intOps && leftIsLoopVar) {
|
||||||
@ -3525,7 +3503,6 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
}
|
}
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
annotateIndexedReceiverSlot(dst, inferFieldReturnClass(receiverClass, ref.name))
|
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
|
val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
|
||||||
@ -3558,7 +3535,6 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
}
|
}
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
annotateIndexedReceiverSlot(dst, inferFieldReturnClass(receiverClass, ref.name))
|
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3582,7 +3558,6 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? {
|
private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? {
|
||||||
val receiver = compileThisRef()
|
val receiver = compileThisRef()
|
||||||
val ownerClass = implicitThisTypeName?.let { resolveTypeNameClass(it) }
|
|
||||||
val fieldId = ref.fieldId() ?: -1
|
val fieldId = ref.fieldId() ?: -1
|
||||||
val methodId = ref.methodId() ?: -1
|
val methodId = ref.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
@ -3609,13 +3584,11 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
}
|
}
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
annotateIndexedReceiverSlot(dst, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
|
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileQualifiedThisFieldSlotRef(ref: QualifiedThisFieldSlotRef): CompiledValue? {
|
private fun compileQualifiedThisFieldSlotRef(ref: QualifiedThisFieldSlotRef): CompiledValue? {
|
||||||
val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
|
val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
|
||||||
val ownerClass = resolveTypeNameClass(ref.receiverTypeName())
|
|
||||||
val fieldId = ref.fieldId() ?: -1
|
val fieldId = ref.fieldId() ?: -1
|
||||||
val methodId = ref.methodId() ?: -1
|
val methodId = ref.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
@ -3642,7 +3615,6 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
}
|
}
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
annotateIndexedReceiverSlot(dst, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
|
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7871,25 +7843,16 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun listElementClassFromDecl(decl: TypeDecl): ObjClass? {
|
private fun listElementClassFromDecl(decl: TypeDecl): ObjClass? {
|
||||||
return when (decl) {
|
val generic = decl as? TypeDecl.Generic ?: return null
|
||||||
is TypeDecl.Generic -> {
|
if (generic.name != "List" || generic.args.size != 1) return null
|
||||||
if (decl.name != "List" || decl.args.size != 1) return null
|
val arg = generic.args.first()
|
||||||
val arg = decl.args.first()
|
val cls = when (arg) {
|
||||||
val cls = when (arg) {
|
is TypeDecl.Simple -> resolveTypeNameClass(arg.name)
|
||||||
is TypeDecl.Simple -> resolveTypeNameClass(arg.name)
|
is TypeDecl.Generic -> resolveTypeNameClass(arg.name)
|
||||||
is TypeDecl.Generic -> resolveTypeNameClass(arg.name)
|
else -> null
|
||||||
else -> null
|
}
|
||||||
}
|
return when (cls) {
|
||||||
when (cls) {
|
ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> cls
|
||||||
ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> cls
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TypeDecl.Simple -> when (decl.name.substringAfterLast('.')) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
|
|
||||||
"String" -> ObjChar.type
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7902,67 +7865,12 @@ class BytecodeCompiler(
|
|||||||
val decl = slotTypeDeclByScopeId[scopeId]?.get(slot) ?: return null
|
val decl = slotTypeDeclByScopeId[scopeId]?.get(slot) ?: return null
|
||||||
listElementClassFromDecl(decl)
|
listElementClassFromDecl(decl)
|
||||||
}
|
}
|
||||||
is ImplicitThisMemberRef -> {
|
else -> null
|
||||||
val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) } ?: return null
|
|
||||||
val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
|
|
||||||
when (fieldClass.className) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
|
|
||||||
"String" -> ObjChar.type
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ThisFieldSlotRef -> {
|
|
||||||
val ownerClass = implicitThisTypeName?.let { resolveTypeNameClass(it) } ?: return null
|
|
||||||
val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
|
|
||||||
when (fieldClass.className) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
|
|
||||||
"String" -> ObjChar.type
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is QualifiedThisFieldSlotRef -> {
|
|
||||||
val ownerClass = resolveTypeNameClass(ref.receiverTypeName()) ?: return null
|
|
||||||
val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
|
|
||||||
when (fieldClass.className) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
|
|
||||||
"String" -> ObjChar.type
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is FieldRef -> {
|
|
||||||
val fieldClass = resolveReceiverClass(ref) ?: return null
|
|
||||||
when (fieldClass.className) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
|
|
||||||
"String" -> ObjChar.type
|
|
||||||
else -> listElementClassFromDecl(TypeDecl.Simple(fieldClass.className, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val receiverClass = resolveReceiverClass(ref) ?: return null
|
|
||||||
when (receiverClass.className) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
|
|
||||||
"String" -> ObjChar.type
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun annotateIndexedReceiverSlot(slot: Int, receiverClass: ObjClass?) {
|
private fun indexElementClass(receiverSlot: Int, targetRef: ObjRef): ObjClass? =
|
||||||
if (receiverClass == null) return
|
listElementClassBySlot[receiverSlot] ?: listElementClassFromReceiverRef(targetRef)
|
||||||
slotObjClass[slot] = receiverClass
|
|
||||||
when (receiverClass.className) {
|
|
||||||
"Buffer", "MutableBuffer", "BitBuffer" -> listElementClassBySlot[slot] = ObjInt.type
|
|
||||||
"String" -> listElementClassBySlot[slot] = ObjChar.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun indexElementClass(receiverSlot: Int, targetRef: ObjRef): ObjClass? {
|
|
||||||
listElementClassBySlot[receiverSlot]?.let { return it }
|
|
||||||
listElementClassFromReceiverRef(targetRef)?.let { return it }
|
|
||||||
val receiverClass = resolveReceiverClass(targetRef) ?: return null
|
|
||||||
return inferFieldReturnClass(receiverClass, "getAt")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun indexElementSlotType(receiverSlot: Int, targetRef: ObjRef): SlotType? =
|
private fun indexElementSlotType(receiverSlot: Int, targetRef: ObjRef): SlotType? =
|
||||||
slotTypeFromClass(indexElementClass(receiverSlot, targetRef))
|
slotTypeFromClass(indexElementClass(receiverSlot, targetRef))
|
||||||
@ -9067,25 +8975,10 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun coerceToArithmeticInt(ref: ObjRef, value: CompiledValue): CompiledValue? {
|
private fun coerceToArithmeticInt(ref: ObjRef, value: CompiledValue): CompiledValue? {
|
||||||
if (value.type == SlotType.INT) return value
|
if (value.type == SlotType.INT) return value
|
||||||
val refSuggestsInt = isIntLikeRef(ref) || inferNumericKind(ref) == NumericKind.INT
|
val refSuggestsInt = inferNumericKind(ref) == NumericKind.INT
|
||||||
val stableNonTemp = !isTempSlot(value.slot) && isStablePrimitiveSourceSlot(value.slot)
|
val stableNonTemp = !isTempSlot(value.slot) && isStablePrimitiveSourceSlot(value.slot)
|
||||||
return when (value.type) {
|
if (!refSuggestsInt && !stableNonTemp) return null
|
||||||
SlotType.UNKNOWN -> {
|
return coerceToLoopInt(value)
|
||||||
if (!refSuggestsInt) return null
|
|
||||||
updateSlotType(value.slot, SlotType.INT)
|
|
||||||
CompiledValue(value.slot, SlotType.INT)
|
|
||||||
}
|
|
||||||
SlotType.OBJ -> {
|
|
||||||
if (!refSuggestsInt && !stableNonTemp) return null
|
|
||||||
coerceToLoopInt(value)?.let { return it }
|
|
||||||
if (!refSuggestsInt) return null
|
|
||||||
val intSlot = allocSlot()
|
|
||||||
builder.emit(Opcode.UNBOX_INT_OBJ, emitAssertObjSlotIsInt(value.slot), intSlot)
|
|
||||||
updateSlotType(intSlot, SlotType.INT)
|
|
||||||
CompiledValue(intSlot, SlotType.INT)
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitAssertObjSlotIsInt(slot: Int): Int {
|
private fun emitAssertObjSlotIsInt(slot: Int): Int {
|
||||||
|
|||||||
@ -92,7 +92,6 @@ class BytecodeStatement private constructor(
|
|||||||
scopeRefPosByName: Map<String, Pos> = emptyMap(),
|
scopeRefPosByName: Map<String, Pos> = emptyMap(),
|
||||||
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
||||||
implicitThisTypeName: String? = null,
|
|
||||||
): Statement {
|
): Statement {
|
||||||
if (statement is BytecodeStatement) return statement
|
if (statement is BytecodeStatement) return statement
|
||||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||||
@ -129,8 +128,7 @@ class BytecodeStatement private constructor(
|
|||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = preparedModuleBindingNames,
|
preparedModuleBindingNames = preparedModuleBindingNames,
|
||||||
scopeRefPosByName = scopeRefPosByName,
|
scopeRefPosByName = scopeRefPosByName,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef,
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
implicitThisTypeName = implicitThisTypeName
|
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
val fn = compiled ?: throw BytecodeCompileException(
|
val fn = compiled ?: throw BytecodeCompileException(
|
||||||
|
|||||||
@ -3094,7 +3094,11 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
|||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
|
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
|
||||||
?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId")
|
?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId")
|
||||||
val type = frame.ensureScope().resolveExtensionReceiverClass(decl.extTypeName)
|
val type = frame.ensureScope()[decl.extTypeName]?.value
|
||||||
|
?: frame.ensureScope().raiseSymbolNotFound("class ${decl.extTypeName} not found")
|
||||||
|
if (type !is ObjClass) {
|
||||||
|
frame.ensureScope().raiseClassCastError("${decl.extTypeName} is not the class instance")
|
||||||
|
}
|
||||||
frame.ensureScope().addExtension(
|
frame.ensureScope().addExtension(
|
||||||
type,
|
type,
|
||||||
decl.property.name,
|
decl.property.name,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -47,46 +47,4 @@ class BitwiseTest {
|
|||||||
// type mismatch should raise
|
// type mismatch should raise
|
||||||
assertFails { e("1 & 2.0") }
|
assertFails { e("1 & 2.0") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBitwiseInference() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
import lyng.buffer
|
|
||||||
class Foo() {
|
|
||||||
val buf = Buffer(64).toMutable()
|
|
||||||
fn fn2(): Int {
|
|
||||||
val tmp = this.buf[1] & 127
|
|
||||||
println("fn2: ", tmp)
|
|
||||||
tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val foo = Foo()
|
|
||||||
assertEquals(0, foo.fn2())
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCustomIndexerIntInference() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
class TestBuffer() {
|
|
||||||
override fn getAt(index): Int = index + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
class Foo() {
|
|
||||||
val buf = TestBuffer()
|
|
||||||
fn fn2(): Int {
|
|
||||||
val tmp = (this.buf[1] & 127) + this.buf[2] * 2 - 1
|
|
||||||
tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val foo = Foo()
|
|
||||||
assertEquals(7, foo.fn2())
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -384,32 +384,6 @@ class OOTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testObjectSingletonSupportsExtensions() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
object X {
|
|
||||||
fun base() = "base"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun X.decorate<T>(value: T): String {
|
|
||||||
this.base() + ":" + value.toString()
|
|
||||||
}
|
|
||||||
val X.tag get() = this.base() + ":tag"
|
|
||||||
|
|
||||||
assertEquals("base", X.base())
|
|
||||||
assertEquals("base:42", X.decorate(42))
|
|
||||||
assertEquals("base:ok", X.decorate("ok"))
|
|
||||||
assertEquals("base:tag", X.tag)
|
|
||||||
|
|
||||||
// Wrapper names should be generated for singleton-object receivers too.
|
|
||||||
assertEquals("base:17", __ext__X__decorate(X, 17))
|
|
||||||
assertEquals("base:tag", __ext_get__X__tag(X))
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testExtensionsAreScopeIsolated() = runTest {
|
fun testExtensionsAreScopeIsolated() = runTest {
|
||||||
val scope1 = Script.newScope()
|
val scope1 = Script.newScope()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user