Add resolved export metadata and fix imported accessors
This commit is contained in:
parent
1a22b60b7d
commit
898ebfa536
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "1.5.9"
|
||||
version = "1.5.10-SNAPSHOT"
|
||||
|
||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||
|
||||
|
||||
@ -509,5 +509,15 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
val isTransient: Boolean = false,
|
||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||
val annotations: List<DeclAnnotation> = emptyList(),
|
||||
)
|
||||
/** Exact source text of the default expression, when parsed from Lyng source. */
|
||||
val defaultSource: String? = null,
|
||||
) {
|
||||
/** Type used at call sites, where ellipsis describes repeated arguments. */
|
||||
val signatureType: TypeDecl
|
||||
get() = if (isEllipsis) TypeDecl.Ellipsis(type) else type
|
||||
|
||||
/** Type visible inside the callable after repeated arguments are collected. */
|
||||
val localType: TypeDecl
|
||||
get() = if (isEllipsis) TypeDecl.Generic("List", listOf(type), false) else type
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.Compiler.Companion.compile
|
||||
import net.sergeych.lyng.bytecode.*
|
||||
import net.sergeych.lyng.highlight.offsetOf
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
@ -3671,7 +3672,12 @@ class Compiler(
|
||||
val effectiveType = if (param.isEllipsis) TypeDecl.Ellipsis(rawType) else rawType
|
||||
lambdaParamTypeDecls += effectiveType
|
||||
if (effectiveType != TypeDecl.TypeAny && effectiveType != TypeDecl.TypeNullableAny) {
|
||||
seedLambdaParamType(param.name, rawType)
|
||||
val localType = if (param.isEllipsis) {
|
||||
TypeDecl.Generic("List", listOf(rawType), false)
|
||||
} else {
|
||||
rawType
|
||||
}
|
||||
seedLambdaParamType(param.name, localType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -3753,7 +3759,7 @@ class Compiler(
|
||||
val returnClass = inferReturnClassFromStatement(body)
|
||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||
argsDeclaration?.params?.forEach { param ->
|
||||
val cls = resolveTypeDeclObjClass(param.type) ?: return@forEach
|
||||
val cls = resolveTypeDeclObjClass(param.localType) ?: return@forEach
|
||||
paramKnownClasses[param.name] = cls
|
||||
}
|
||||
val returnLabels = label?.let { setOf(it) } ?: emptySet()
|
||||
@ -4289,10 +4295,12 @@ class Compiler(
|
||||
}
|
||||
|
||||
var defaultValue: Obj? = null
|
||||
cc.ifNextIs(Token.Type.ASSIGN) {
|
||||
var defaultSource: String? = null
|
||||
cc.ifNextIs(Token.Type.ASSIGN) { assignment ->
|
||||
val expr = parseExpression()
|
||||
?: throw ScriptError(cc.current().pos, "Expected default value expression")
|
||||
defaultValue = wrapBytecode(expr)
|
||||
defaultSource = extractDefaultArgumentSource(assignment.pos)
|
||||
}
|
||||
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
||||
result += ArgsDeclaration.Item(
|
||||
@ -4305,7 +4313,8 @@ class Compiler(
|
||||
effectiveAccess,
|
||||
visibility,
|
||||
isTransient,
|
||||
annotationSpecs = annotationSpecs
|
||||
annotationSpecs = annotationSpecs,
|
||||
defaultSource = defaultSource,
|
||||
)
|
||||
|
||||
// important: valid argument list continues with ',' and ends with '->' or ')'
|
||||
@ -4343,6 +4352,48 @@ class Compiler(
|
||||
return ArgsDeclaration(result, endTokenType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a default expression from its original source. Token positions for strings point
|
||||
* inside their quotes, so reconstructing the range from the first/last expression token loses
|
||||
* delimiters. Scan the source instead, respecting nested groups and quoted literals.
|
||||
*/
|
||||
private fun extractDefaultArgumentSource(assignmentPos: Pos): String {
|
||||
val source = assignmentPos.source
|
||||
val text = source.text
|
||||
val start = source.offsetOf(assignmentPos) + 1
|
||||
var index = start
|
||||
var round = 0
|
||||
var square = 0
|
||||
var curly = 0
|
||||
var quote: Char? = null
|
||||
var escaped = false
|
||||
while (index < text.length) {
|
||||
val ch = text[index]
|
||||
val activeQuote = quote
|
||||
if (activeQuote != null) {
|
||||
if (escaped) escaped = false
|
||||
else if (ch == '\\') escaped = true
|
||||
else if (ch == activeQuote) quote = null
|
||||
index++
|
||||
continue
|
||||
}
|
||||
when (ch) {
|
||||
'"', '\'', '`' -> quote = ch
|
||||
'(' -> round++
|
||||
')' -> if (round == 0 && square == 0 && curly == 0) break else round--
|
||||
'[' -> square++
|
||||
']' -> square--
|
||||
'{' -> curly++
|
||||
'}' -> curly--
|
||||
',' -> if (round == 0 && square == 0 && curly == 0) break
|
||||
'.' -> if (round == 0 && square == 0 && curly == 0 &&
|
||||
text.startsWith("...", index)) break
|
||||
}
|
||||
index++
|
||||
}
|
||||
return text.substring(start, index).trim()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private fun parseTypeDeclaration(): TypeDecl {
|
||||
return parseTypeDeclarationWithMini().first
|
||||
@ -8666,7 +8717,7 @@ class Compiler(
|
||||
|
||||
constructorArgsDeclaration?.params?.forEach { param ->
|
||||
if (param.accessType != null) {
|
||||
val declClass = resolveTypeDeclObjClass(param.type)
|
||||
val declClass = resolveTypeDeclObjClass(param.localType)
|
||||
if (declClass != null) {
|
||||
classFieldTypesByName.getOrPut(qualifiedName) { mutableMapOf() }[param.name] = declClass
|
||||
}
|
||||
@ -8684,8 +8735,8 @@ class Compiler(
|
||||
val classParamTypeDeclMap = slotTypeDeclByScopeId.getOrPut(classSlotPlan.id) { mutableMapOf() }
|
||||
for (param in ctorDecl.params) {
|
||||
val slot = classSlotPlan.slots[param.name]?.index ?: continue
|
||||
classParamTypeDeclMap[slot] = param.type
|
||||
resolveTypeDeclObjClass(param.type)?.let { classParamTypeMap[slot] = it }
|
||||
classParamTypeDeclMap[slot] = param.localType
|
||||
resolveTypeDeclObjClass(param.localType)?.let { classParamTypeMap[slot] = it }
|
||||
}
|
||||
}
|
||||
val ctorForcedLocalSlots = LinkedHashMap<String, Int>()
|
||||
@ -9984,7 +10035,7 @@ class Compiler(
|
||||
classMemberTypeDeclByName.getOrPut(parentContext.name) { mutableMapOf() }[name] = TypeDecl.Function(
|
||||
receiver = receiverTypeDecl,
|
||||
contextReceivers = contextReceiverTypeDecls,
|
||||
params = argsDeclaration.params.map { it.type },
|
||||
params = argsDeclaration.params.map { it.signatureType },
|
||||
returnType = returnTypeDecl ?: TypeDecl.TypeAny,
|
||||
nullable = false
|
||||
)
|
||||
@ -10087,19 +10138,19 @@ class Compiler(
|
||||
argsDeclaration = wrapDefaultArgsBytecode(argsDeclaration, forcedLocalSlots, paramSlotPlan.id)
|
||||
val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
|
||||
val rangeParamNames = argsDeclaration.params
|
||||
.filter { isRangeType(it.type) }
|
||||
.filter { isRangeType(it.localType) }
|
||||
.map { it.name }
|
||||
.toSet()
|
||||
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||
for (param in argsDeclaration.params) {
|
||||
val cls = resolveTypeDeclObjClass(param.type) ?: continue
|
||||
val cls = resolveTypeDeclObjClass(param.localType) ?: continue
|
||||
val slot = paramSlotPlan.slots[param.name]?.index ?: continue
|
||||
paramTypeMap[slot] = cls
|
||||
}
|
||||
for (param in argsDeclaration.params) {
|
||||
val slot = paramSlotPlan.slots[param.name]?.index ?: continue
|
||||
paramTypeDeclMap[slot] = param.type
|
||||
paramTypeDeclMap[slot] = param.localType
|
||||
}
|
||||
|
||||
// Parse function body while tracking declared locals to compute precise capacity hints
|
||||
@ -10160,7 +10211,7 @@ class Compiler(
|
||||
val memberTypeDecl = TypeDecl.Function(
|
||||
receiver = receiverTypeDecl,
|
||||
contextReceivers = contextReceiverTypeDecls,
|
||||
params = argsDeclaration.params.map { it.type },
|
||||
params = argsDeclaration.params.map { it.signatureType },
|
||||
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
||||
nullable = false
|
||||
)
|
||||
@ -10194,7 +10245,7 @@ class Compiler(
|
||||
if (!compileBytecode) return@let stmt
|
||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||
for (param in argsDeclaration.params) {
|
||||
val cls = resolveTypeDeclObjClass(param.type) ?: continue
|
||||
val cls = resolveTypeDeclObjClass(param.localType) ?: continue
|
||||
paramKnownClasses[param.name] = cls
|
||||
}
|
||||
wrapFunctionBytecode(
|
||||
@ -10357,7 +10408,7 @@ class Compiler(
|
||||
typeDecl = if (isDelegated) null else TypeDecl.Function(
|
||||
receiver = receiverTypeDecl,
|
||||
contextReceivers = contextReceiverTypeDecls,
|
||||
params = argsDeclaration.params.map { it.type },
|
||||
params = argsDeclaration.params.map { it.signatureType },
|
||||
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
||||
nullable = false
|
||||
),
|
||||
@ -10366,7 +10417,32 @@ class Compiler(
|
||||
captureSlots = captureSlots,
|
||||
slotIndex = declSlotIndex,
|
||||
scopeId = declScopeId,
|
||||
startPos = start
|
||||
startPos = start,
|
||||
resolvedMetadata = ResolvedFunctionMetadata(
|
||||
name = name,
|
||||
namePos = nameStartPos,
|
||||
declarationPos = start,
|
||||
visibility = visibility,
|
||||
annotations = declarationAnnotationSpecs.map { it.name },
|
||||
typeParams = mergedTypeParamDecls,
|
||||
parameters = argsDeclaration.params.map { parameter ->
|
||||
ResolvedFunctionParameter(
|
||||
name = parameter.name,
|
||||
type = parameter.type,
|
||||
isEllipsis = parameter.isEllipsis,
|
||||
hasDefault = parameter.defaultValue != null,
|
||||
defaultSource = parameter.defaultSource,
|
||||
pos = parameter.pos,
|
||||
)
|
||||
},
|
||||
functionType = TypeDecl.Function(
|
||||
receiver = receiverTypeDecl,
|
||||
contextReceivers = contextReceiverTypeDecls,
|
||||
params = argsDeclaration.params.map { it.signatureType },
|
||||
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
||||
nullable = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
val declaredFn = FunctionDeclStatement(spec)
|
||||
if (isStatic && parentIsClassBody) {
|
||||
@ -11725,6 +11801,16 @@ class Compiler(
|
||||
return script
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile [source] and return semantic top-level function metadata without evaluating it.
|
||||
*/
|
||||
suspend fun resolveFunctionMetadata(
|
||||
source: Source,
|
||||
importManager: ImportProvider,
|
||||
): List<ResolvedFunctionMetadata> =
|
||||
compileWithResolution(source, importManager, compileBytecode = false)
|
||||
.resolvedFunctionMetadata()
|
||||
|
||||
suspend fun dryRun(source: Source, importManager: ImportProvider): ResolutionReport {
|
||||
return CompileTimeResolver.dryRun(source, importManager)
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ data class FunctionDeclSpec(
|
||||
val slotIndex: Int?,
|
||||
val scopeId: Int?,
|
||||
val startPos: Pos,
|
||||
val resolvedMetadata: ResolvedFunctionMetadata,
|
||||
)
|
||||
|
||||
internal suspend fun executeFunctionDecl(
|
||||
|
||||
@ -18,6 +18,7 @@ package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.bytecode.CmdFrame
|
||||
import net.sergeych.lyng.bytecode.CmdVm
|
||||
import net.sergeych.lyng.bytecode.seedFrameLocalsFromScope
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjNull
|
||||
|
||||
@ -34,6 +35,7 @@ class PropertyAccessorStatement(
|
||||
val bytecodeStmt = requireBytecodeBody(scope, body, "property accessor")
|
||||
val fn = bytecodeStmt.bytecodeFunction()
|
||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||
seedFrameLocalsFromScope(frame, scope)
|
||||
val slotPlan = fn.localSlotPlanByName()
|
||||
val slotIndex = slotPlan[argName]
|
||||
?: scope.raiseIllegalState("property accessor argument $argName missing from slot plan")
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Compiler-resolved metadata for a function parameter.
|
||||
*
|
||||
* [defaultSource] preserves the source expression for tools that generate declarations. It is
|
||||
* null for parameters without defaults and may also be null for declarations assembled by a host
|
||||
* rather than parsed from source; use [hasDefault] to distinguish those cases.
|
||||
*/
|
||||
data class ResolvedFunctionParameter(
|
||||
val name: String,
|
||||
val type: TypeDecl,
|
||||
val isEllipsis: Boolean,
|
||||
val hasDefault: Boolean,
|
||||
val defaultSource: String?,
|
||||
val pos: Pos,
|
||||
)
|
||||
|
||||
/**
|
||||
* Semantic function declaration information produced by the compiler without evaluating source.
|
||||
*
|
||||
* Unlike the editor Mini-AST, this descriptor contains compiler-resolved parameter and return
|
||||
* types, including an inferred return type when inference succeeds. Annotation names describe the
|
||||
* declaration syntax; annotation functions are not executed while this metadata is collected.
|
||||
*/
|
||||
data class ResolvedFunctionMetadata(
|
||||
val name: String,
|
||||
val namePos: Pos,
|
||||
val declarationPos: Pos,
|
||||
val visibility: Visibility,
|
||||
val annotations: List<String>,
|
||||
val typeParams: List<TypeDecl.TypeParam>,
|
||||
val parameters: List<ResolvedFunctionParameter>,
|
||||
val functionType: TypeDecl.Function,
|
||||
) {
|
||||
val returnType: TypeDecl get() = functionType.returnType
|
||||
}
|
||||
@ -55,6 +55,10 @@ class Script(
|
||||
) : Statement() {
|
||||
fun statements(): List<Statement> = statements
|
||||
|
||||
/** Compiler-resolved metadata for top-level function declarations in source order. */
|
||||
fun resolvedFunctionMetadata(): List<ResolvedFunctionMetadata> =
|
||||
statements.filterIsInstance<FunctionDeclStatement>().map { it.spec.resolvedMetadata }
|
||||
|
||||
/**
|
||||
* Explicitly apply this script's import/module bindings to [scope] without executing the script.
|
||||
* This is intended for embedding scenarios where the host owns scope lifecycle and wants
|
||||
|
||||
@ -3261,12 +3261,14 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
|
||||
?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId")
|
||||
val type = frame.ensureScope().resolveExtensionReceiverClass(decl.extTypeName)
|
||||
frame.ensureScope().addExtension(
|
||||
val declarationScope = frame.ensureScope()
|
||||
val property = decl.property.withDeclarationScope(declarationScope)
|
||||
val type = declarationScope.resolveExtensionReceiverClass(decl.extTypeName)
|
||||
declarationScope.addExtension(
|
||||
type,
|
||||
decl.property.name,
|
||||
property.name,
|
||||
ObjRecord(
|
||||
decl.property,
|
||||
property,
|
||||
isMutable = false,
|
||||
visibility = decl.visibility,
|
||||
writeVisibility = decl.setterVisibility,
|
||||
@ -3274,9 +3276,9 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
||||
type = ObjRecord.Type.Property
|
||||
)
|
||||
)
|
||||
val getterName = extensionPropertyGetterName(decl.extTypeName, decl.property.name)
|
||||
val getterWrapper = ObjExtensionPropertyGetterCallable(decl.property.name, decl.property)
|
||||
frame.ensureScope().addItem(
|
||||
val getterName = extensionPropertyGetterName(decl.extTypeName, property.name)
|
||||
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
|
||||
declarationScope.addItem(
|
||||
getterName,
|
||||
false,
|
||||
getterWrapper,
|
||||
@ -3288,10 +3290,10 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
||||
if (getterLocal != null) {
|
||||
frame.setObjUnchecked(frame.fn.scopeSlotCount + getterLocal, getterWrapper)
|
||||
}
|
||||
if (decl.property.setter != null) {
|
||||
val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name)
|
||||
val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property)
|
||||
frame.ensureScope()
|
||||
if (property.setter != null) {
|
||||
val setterName = extensionPropertySetterName(decl.extTypeName, property.name)
|
||||
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
|
||||
declarationScope
|
||||
.addItem(
|
||||
setterName,
|
||||
false,
|
||||
|
||||
@ -56,9 +56,9 @@ internal fun trySeedFrameLocalsFromScopeFast(frame: CmdFrame, scope: Scope): Boo
|
||||
record.type == ObjRecord.Type.Property -> return false
|
||||
record.value is ObjProperty -> return false
|
||||
else -> when (val direct = record.value) {
|
||||
is FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
||||
is RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
||||
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
||||
is FrameSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: return false
|
||||
is RecordSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: return false
|
||||
is ScopeSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: return false
|
||||
else -> direct
|
||||
}
|
||||
}
|
||||
@ -86,9 +86,9 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||
scope.resolve(record, name)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is net.sergeych.lyng.FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.FrameSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.RecordSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.ScopeSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: direct
|
||||
else -> direct
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,15 +32,21 @@ import net.sergeych.lyng.executeBytecodeWithSeed
|
||||
class ObjProperty(
|
||||
val name: String,
|
||||
val getter: Obj?,
|
||||
val setter: Obj?
|
||||
val setter: Obj?,
|
||||
private val declarationScope: Scope? = null
|
||||
) : Obj() {
|
||||
|
||||
fun withDeclarationScope(scope: Scope): ObjProperty =
|
||||
ObjProperty(name, getter, setter, scope)
|
||||
|
||||
suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): Obj {
|
||||
val g = getter ?: scope.raiseError("property $name has no getter")
|
||||
// Execute getter in a child scope of the instance with 'this' properly set
|
||||
// Match extension function behavior (access to instance scope + call scope).
|
||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
|
||||
val receiverCallScope = scope.applyClosure(instanceScope)
|
||||
val execBase = declarationScope?.let(receiverCallScope::applyClosure) ?: receiverCallScope
|
||||
val execScope = execBase.createChildScope(newThisObj = instance)
|
||||
execScope.currentClassCtx = declaringClass
|
||||
(g as? BytecodeCallable)?.callOnFast(execScope)?.let { return it }
|
||||
return when (g) {
|
||||
@ -60,7 +66,9 @@ class ObjProperty(
|
||||
// Execute setter in a child scope of the instance with 'this' properly set and the value as an argument
|
||||
// Match extension function behavior (access to instance scope + call scope).
|
||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
||||
val receiverCallScope = scope.applyClosure(instanceScope)
|
||||
val execBase = declarationScope?.let(receiverCallScope::applyClosure) ?: receiverCallScope
|
||||
val execScope = execBase.createChildScope(args = Arguments(value), newThisObj = instance)
|
||||
execScope.currentClassCtx = declaringClass
|
||||
(s as? BytecodeCallable)?.callOnFast(execScope)?.let { return }
|
||||
when (s) {
|
||||
|
||||
@ -249,4 +249,39 @@ class ScriptImportPreparationTest {
|
||||
val result = script.execute(manager.newStdScope()) as ObjString
|
||||
assertEquals("<html><h3>Imported</h3></html>", result.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun importedExtensionPropertyAccessorsUseDeclaringModuleScope() = runTest {
|
||||
val manager = Script.defaultImportManager.copy().apply {
|
||||
addTextPackages(
|
||||
"""
|
||||
package imported.extprop
|
||||
|
||||
private var prefix = "module"
|
||||
|
||||
private fun decorate(value: String): String = prefix + ":" + value
|
||||
|
||||
var String.decorated: String
|
||||
get() = decorate(this)
|
||||
set(value) { prefix = value }
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
val script = Compiler.compile(
|
||||
Source(
|
||||
"<extension-property-import>",
|
||||
"""
|
||||
import imported.extprop
|
||||
|
||||
val before = "ok".decorated
|
||||
"ignored".decorated = "updated"
|
||||
before + "|" + "ok".decorated
|
||||
""".trimIndent()
|
||||
),
|
||||
manager
|
||||
)
|
||||
|
||||
val result = script.execute(manager.newStdScope()) as ObjString
|
||||
assertEquals("module:ok|updated:ok", result.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.bridge.globalBinder
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ImportedExternExtensionPropertyTest {
|
||||
@Test
|
||||
fun importedExtensionGetterCanCallExternFromItsImportedRuntimeModule() = runTest {
|
||||
val imports = Script.defaultImportManager.copy()
|
||||
imports.addPackage("test.contract.runtime") { module ->
|
||||
module.eval(
|
||||
"""
|
||||
package test.contract.runtime
|
||||
|
||||
class ContractContext(val label: String)
|
||||
class ContractRegistryBase
|
||||
class ContractRegistry : ContractRegistryBase
|
||||
|
||||
extern val Contracts: ContractRegistry
|
||||
extern fun contractHandle(nameOrId: String): ContractContext
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val contextClass = module.resolve(
|
||||
requireNotNull(module["ContractContext"]),
|
||||
"ContractContext"
|
||||
) as ObjClass
|
||||
val registryClass = module.resolve(
|
||||
requireNotNull(module["ContractRegistry"]),
|
||||
"ContractRegistry"
|
||||
) as ObjClass
|
||||
val context = contextClass.callOn(
|
||||
module.createChildScope(args = Arguments(ObjString("bound")))
|
||||
)
|
||||
val registry = registryClass.callOn(module.createChildScope())
|
||||
module.globalBinder().bindGlobalVarRaw("Contracts", get = { registry })
|
||||
module.globalBinder().bindGlobalFunRaw("contractHandle") { _, args ->
|
||||
assertEquals("Alpha", (args.firstAndOnly() as ObjString).value)
|
||||
context
|
||||
}
|
||||
}
|
||||
imports.addTextPackages(
|
||||
"""
|
||||
package test.contract.Alpha
|
||||
import test.contract.runtime
|
||||
|
||||
class AlphaContractAbi(val target: ContractContext)
|
||||
|
||||
val ContractRegistryBase.AlphaHandle: ContractContext
|
||||
get() = contractHandle("Alpha")
|
||||
|
||||
val ContractRegistryBase.Alpha: AlphaContractAbi
|
||||
get() = AlphaContractAbi(contractHandle("Alpha"))
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val scope = imports.newStdScope()
|
||||
val handleResult = scope.eval(
|
||||
"""
|
||||
import test.contract.runtime
|
||||
import test.contract.Alpha
|
||||
|
||||
Contracts.AlphaHandle.label
|
||||
""".trimIndent()
|
||||
) as ObjString
|
||||
assertEquals("bound", handleResult.value)
|
||||
|
||||
val result = scope.eval(
|
||||
"""
|
||||
import test.contract.runtime
|
||||
import test.contract.Alpha
|
||||
|
||||
Contracts.Alpha.target.label
|
||||
""".trimIndent()
|
||||
) as ObjString
|
||||
|
||||
assertEquals("bound", result.value)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ResolvedFunctionMetadataTest {
|
||||
|
||||
@Test
|
||||
fun resolvesCompleteExportSignatureWithoutExecutingSource() = runTest {
|
||||
val source = Source(
|
||||
"metadata.lyng",
|
||||
"""
|
||||
@Export
|
||||
fun exported<T: Object = String>(
|
||||
head: T,
|
||||
rest: T...,
|
||||
limit: Int = 7,
|
||||
suffix: String = "!",
|
||||
): List<T> { [] }
|
||||
|
||||
fun helper(value: String): String { value }
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val functions = Compiler.resolveFunctionMetadata(source, Script.defaultImportManager)
|
||||
val exported = functions.single { it.name == "exported" }
|
||||
val helper = functions.single { it.name == "helper" }
|
||||
|
||||
assertEquals(listOf("Export"), exported.annotations)
|
||||
assertTrue(helper.annotations.isEmpty())
|
||||
|
||||
assertEquals("metadata.lyng", exported.namePos.source.fileName)
|
||||
assertSame(source, exported.namePos.source)
|
||||
assertEquals("exported", exported.namePos.currentLine.substring(exported.namePos.column, exported.namePos.column + 8))
|
||||
assertSame(source, exported.declarationPos.source)
|
||||
|
||||
val typeParameter = exported.typeParams.single()
|
||||
assertEquals("T", typeParameter.name)
|
||||
assertEquals(TypeDecl.Variance.Invariant, typeParameter.variance)
|
||||
assertSimple(typeParameter.bound, "Object")
|
||||
assertSimple(typeParameter.defaultType, "String")
|
||||
|
||||
assertEquals(listOf("head", "rest", "limit", "suffix"), exported.parameters.map { it.name })
|
||||
assertTypeVariable(exported.parameters[0].type, "T")
|
||||
assertFalse(exported.parameters[0].isEllipsis)
|
||||
assertFalse(exported.parameters[0].hasDefault)
|
||||
assertNull(exported.parameters[0].defaultSource)
|
||||
|
||||
assertTypeVariable(exported.parameters[1].type, "T")
|
||||
assertTrue(exported.parameters[1].isEllipsis)
|
||||
assertFalse(exported.parameters[1].hasDefault)
|
||||
assertNull(exported.parameters[1].defaultSource)
|
||||
|
||||
assertSimple(exported.parameters[2].type, "Int")
|
||||
assertFalse(exported.parameters[2].isEllipsis)
|
||||
assertTrue(exported.parameters[2].hasDefault)
|
||||
assertEquals("7", exported.parameters[2].defaultSource)
|
||||
assertTrue(exported.parameters[3].hasDefault)
|
||||
assertEquals("\"!\"", exported.parameters[3].defaultSource)
|
||||
|
||||
val returnType = assertIs<TypeDecl.Generic>(exported.returnType)
|
||||
assertEquals("List", returnType.name)
|
||||
assertEquals(1, returnType.args.size)
|
||||
assertTypeVariable(returnType.args.single(), "T")
|
||||
|
||||
val functionType = assertIs<TypeDecl.Function>(exported.functionType)
|
||||
assertNull(functionType.receiver)
|
||||
assertTrue(functionType.contextReceivers.isEmpty())
|
||||
assertEquals(4, functionType.params.size)
|
||||
assertTypeVariable(functionType.params[0], "T")
|
||||
val variadicType = assertIs<TypeDecl.Ellipsis>(functionType.params[1])
|
||||
assertTypeVariable(variadicType.elementType, "T")
|
||||
assertSimple(functionType.params[2], "Int")
|
||||
assertGenericListOfTypeVariable(functionType.returnType, "T")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reportsCompilerInferredReturnType() = runTest {
|
||||
val source = Source(
|
||||
"inferred.lyng",
|
||||
"""
|
||||
@Export
|
||||
fun increment(value: Int) = value + 1
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val function = Compiler.resolveFunctionMetadata(source, Script.defaultImportManager).single()
|
||||
|
||||
assertEquals("increment", function.name)
|
||||
assertEquals(listOf("Export"), function.annotations)
|
||||
assertSimple(function.returnType, "Int")
|
||||
assertSimple(assertIs<TypeDecl.Function>(function.functionType).returnType, "Int")
|
||||
}
|
||||
|
||||
private fun assertSimple(type: TypeDecl?, name: String, nullable: Boolean = false) {
|
||||
val simple = assertIs<TypeDecl.Simple>(type)
|
||||
assertEquals(name, simple.name)
|
||||
assertEquals(nullable, simple.isNullable)
|
||||
}
|
||||
|
||||
private fun assertTypeVariable(type: TypeDecl?, name: String, nullable: Boolean = false) {
|
||||
val variable = assertIs<TypeDecl.TypeVar>(type)
|
||||
assertEquals(name, variable.name)
|
||||
assertEquals(nullable, variable.isNullable)
|
||||
}
|
||||
|
||||
private fun assertGenericListOfTypeVariable(type: TypeDecl?, variableName: String) {
|
||||
val generic = assertIs<TypeDecl.Generic>(type)
|
||||
assertEquals("List", generic.name)
|
||||
assertEquals(1, generic.args.size)
|
||||
assertTypeVariable(generic.args.single(), variableName)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class VariadicParameterTypingTest {
|
||||
@Test
|
||||
fun typedVariadicParameterIsAListInsideFunctionBody() = runTest {
|
||||
val result = Script.newScope().eval(
|
||||
"""
|
||||
fun join(prefix: String, values: Int...): String =
|
||||
prefix + values.joinToString(",")
|
||||
|
||||
fun sum(values: Int...): Int {
|
||||
var result = 0
|
||||
for (value in values) result += value
|
||||
return result
|
||||
}
|
||||
|
||||
join("n=", 1, 2, 3) + "|" + sum(4, 5, 6)
|
||||
""".trimIndent()
|
||||
) as ObjString
|
||||
|
||||
assertEquals("n=1,2,3|15", result.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun variadicMetadataKeepsElementTypeInEllipsisSignature() = runTest {
|
||||
val metadata = Compiler.resolveFunctionMetadata(
|
||||
Source("variadic-metadata", "fun join(values: Int...): String = values.joinToString(\",\")"),
|
||||
Script.defaultImportManager,
|
||||
).single()
|
||||
|
||||
val parameter = metadata.parameters.single()
|
||||
assertEquals("Int", (parameter.type as TypeDecl.Simple).name)
|
||||
val signatureType = assertIs<TypeDecl.Ellipsis>(metadata.functionType.params.single())
|
||||
assertEquals("Int", (signatureType.elementType as TypeDecl.Simple).name)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user