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
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
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
|
// 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 isTransient: Boolean = false,
|
||||||
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
val annotationSpecs: List<ParsedDeclAnnotation> = emptyList(),
|
||||||
val annotations: List<DeclAnnotation> = 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.Compiler.Companion.compile
|
||||||
import net.sergeych.lyng.bytecode.*
|
import net.sergeych.lyng.bytecode.*
|
||||||
|
import net.sergeych.lyng.highlight.offsetOf
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
@ -3671,7 +3672,12 @@ class Compiler(
|
|||||||
val effectiveType = if (param.isEllipsis) TypeDecl.Ellipsis(rawType) else rawType
|
val effectiveType = if (param.isEllipsis) TypeDecl.Ellipsis(rawType) else rawType
|
||||||
lambdaParamTypeDecls += effectiveType
|
lambdaParamTypeDecls += effectiveType
|
||||||
if (effectiveType != TypeDecl.TypeAny && effectiveType != TypeDecl.TypeNullableAny) {
|
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 {
|
} else {
|
||||||
@ -3753,7 +3759,7 @@ class Compiler(
|
|||||||
val returnClass = inferReturnClassFromStatement(body)
|
val returnClass = inferReturnClassFromStatement(body)
|
||||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||||
argsDeclaration?.params?.forEach { param ->
|
argsDeclaration?.params?.forEach { param ->
|
||||||
val cls = resolveTypeDeclObjClass(param.type) ?: return@forEach
|
val cls = resolveTypeDeclObjClass(param.localType) ?: return@forEach
|
||||||
paramKnownClasses[param.name] = cls
|
paramKnownClasses[param.name] = cls
|
||||||
}
|
}
|
||||||
val returnLabels = label?.let { setOf(it) } ?: emptySet()
|
val returnLabels = label?.let { setOf(it) } ?: emptySet()
|
||||||
@ -4289,10 +4295,12 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var defaultValue: Obj? = null
|
var defaultValue: Obj? = null
|
||||||
cc.ifNextIs(Token.Type.ASSIGN) {
|
var defaultSource: String? = null
|
||||||
|
cc.ifNextIs(Token.Type.ASSIGN) { assignment ->
|
||||||
val expr = parseExpression()
|
val expr = parseExpression()
|
||||||
?: throw ScriptError(cc.current().pos, "Expected default value expression")
|
?: throw ScriptError(cc.current().pos, "Expected default value expression")
|
||||||
defaultValue = wrapBytecode(expr)
|
defaultValue = wrapBytecode(expr)
|
||||||
|
defaultSource = extractDefaultArgumentSource(assignment.pos)
|
||||||
}
|
}
|
||||||
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
||||||
result += ArgsDeclaration.Item(
|
result += ArgsDeclaration.Item(
|
||||||
@ -4305,7 +4313,8 @@ class Compiler(
|
|||||||
effectiveAccess,
|
effectiveAccess,
|
||||||
visibility,
|
visibility,
|
||||||
isTransient,
|
isTransient,
|
||||||
annotationSpecs = annotationSpecs
|
annotationSpecs = annotationSpecs,
|
||||||
|
defaultSource = defaultSource,
|
||||||
)
|
)
|
||||||
|
|
||||||
// important: valid argument list continues with ',' and ends with '->' or ')'
|
// important: valid argument list continues with ',' and ends with '->' or ')'
|
||||||
@ -4343,6 +4352,48 @@ class Compiler(
|
|||||||
return ArgsDeclaration(result, endTokenType)
|
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")
|
@Suppress("unused")
|
||||||
private fun parseTypeDeclaration(): TypeDecl {
|
private fun parseTypeDeclaration(): TypeDecl {
|
||||||
return parseTypeDeclarationWithMini().first
|
return parseTypeDeclarationWithMini().first
|
||||||
@ -8666,7 +8717,7 @@ class Compiler(
|
|||||||
|
|
||||||
constructorArgsDeclaration?.params?.forEach { param ->
|
constructorArgsDeclaration?.params?.forEach { param ->
|
||||||
if (param.accessType != null) {
|
if (param.accessType != null) {
|
||||||
val declClass = resolveTypeDeclObjClass(param.type)
|
val declClass = resolveTypeDeclObjClass(param.localType)
|
||||||
if (declClass != null) {
|
if (declClass != null) {
|
||||||
classFieldTypesByName.getOrPut(qualifiedName) { mutableMapOf() }[param.name] = declClass
|
classFieldTypesByName.getOrPut(qualifiedName) { mutableMapOf() }[param.name] = declClass
|
||||||
}
|
}
|
||||||
@ -8684,8 +8735,8 @@ class Compiler(
|
|||||||
val classParamTypeDeclMap = slotTypeDeclByScopeId.getOrPut(classSlotPlan.id) { mutableMapOf() }
|
val classParamTypeDeclMap = slotTypeDeclByScopeId.getOrPut(classSlotPlan.id) { mutableMapOf() }
|
||||||
for (param in ctorDecl.params) {
|
for (param in ctorDecl.params) {
|
||||||
val slot = classSlotPlan.slots[param.name]?.index ?: continue
|
val slot = classSlotPlan.slots[param.name]?.index ?: continue
|
||||||
classParamTypeDeclMap[slot] = param.type
|
classParamTypeDeclMap[slot] = param.localType
|
||||||
resolveTypeDeclObjClass(param.type)?.let { classParamTypeMap[slot] = it }
|
resolveTypeDeclObjClass(param.localType)?.let { classParamTypeMap[slot] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val ctorForcedLocalSlots = LinkedHashMap<String, Int>()
|
val ctorForcedLocalSlots = LinkedHashMap<String, Int>()
|
||||||
@ -9984,7 +10035,7 @@ class Compiler(
|
|||||||
classMemberTypeDeclByName.getOrPut(parentContext.name) { mutableMapOf() }[name] = TypeDecl.Function(
|
classMemberTypeDeclByName.getOrPut(parentContext.name) { mutableMapOf() }[name] = TypeDecl.Function(
|
||||||
receiver = receiverTypeDecl,
|
receiver = receiverTypeDecl,
|
||||||
contextReceivers = contextReceiverTypeDecls,
|
contextReceivers = contextReceiverTypeDecls,
|
||||||
params = argsDeclaration.params.map { it.type },
|
params = argsDeclaration.params.map { it.signatureType },
|
||||||
returnType = returnTypeDecl ?: TypeDecl.TypeAny,
|
returnType = returnTypeDecl ?: TypeDecl.TypeAny,
|
||||||
nullable = false
|
nullable = false
|
||||||
)
|
)
|
||||||
@ -10087,19 +10138,19 @@ class Compiler(
|
|||||||
argsDeclaration = wrapDefaultArgsBytecode(argsDeclaration, forcedLocalSlots, paramSlotPlan.id)
|
argsDeclaration = wrapDefaultArgsBytecode(argsDeclaration, forcedLocalSlots, paramSlotPlan.id)
|
||||||
val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
|
val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
|
||||||
val rangeParamNames = argsDeclaration.params
|
val rangeParamNames = argsDeclaration.params
|
||||||
.filter { isRangeType(it.type) }
|
.filter { isRangeType(it.localType) }
|
||||||
.map { it.name }
|
.map { it.name }
|
||||||
.toSet()
|
.toSet()
|
||||||
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||||
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||||
for (param in argsDeclaration.params) {
|
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
|
val slot = paramSlotPlan.slots[param.name]?.index ?: continue
|
||||||
paramTypeMap[slot] = cls
|
paramTypeMap[slot] = cls
|
||||||
}
|
}
|
||||||
for (param in argsDeclaration.params) {
|
for (param in argsDeclaration.params) {
|
||||||
val slot = paramSlotPlan.slots[param.name]?.index ?: continue
|
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
|
// Parse function body while tracking declared locals to compute precise capacity hints
|
||||||
@ -10160,7 +10211,7 @@ class Compiler(
|
|||||||
val memberTypeDecl = TypeDecl.Function(
|
val memberTypeDecl = TypeDecl.Function(
|
||||||
receiver = receiverTypeDecl,
|
receiver = receiverTypeDecl,
|
||||||
contextReceivers = contextReceiverTypeDecls,
|
contextReceivers = contextReceiverTypeDecls,
|
||||||
params = argsDeclaration.params.map { it.type },
|
params = argsDeclaration.params.map { it.signatureType },
|
||||||
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
||||||
nullable = false
|
nullable = false
|
||||||
)
|
)
|
||||||
@ -10194,7 +10245,7 @@ class Compiler(
|
|||||||
if (!compileBytecode) return@let stmt
|
if (!compileBytecode) return@let stmt
|
||||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||||
for (param in argsDeclaration.params) {
|
for (param in argsDeclaration.params) {
|
||||||
val cls = resolveTypeDeclObjClass(param.type) ?: continue
|
val cls = resolveTypeDeclObjClass(param.localType) ?: continue
|
||||||
paramKnownClasses[param.name] = cls
|
paramKnownClasses[param.name] = cls
|
||||||
}
|
}
|
||||||
wrapFunctionBytecode(
|
wrapFunctionBytecode(
|
||||||
@ -10357,7 +10408,7 @@ class Compiler(
|
|||||||
typeDecl = if (isDelegated) null else TypeDecl.Function(
|
typeDecl = if (isDelegated) null else TypeDecl.Function(
|
||||||
receiver = receiverTypeDecl,
|
receiver = receiverTypeDecl,
|
||||||
contextReceivers = contextReceiverTypeDecls,
|
contextReceivers = contextReceiverTypeDecls,
|
||||||
params = argsDeclaration.params.map { it.type },
|
params = argsDeclaration.params.map { it.signatureType },
|
||||||
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
returnType = inferredReturnDecl ?: TypeDecl.TypeAny,
|
||||||
nullable = false
|
nullable = false
|
||||||
),
|
),
|
||||||
@ -10366,7 +10417,32 @@ class Compiler(
|
|||||||
captureSlots = captureSlots,
|
captureSlots = captureSlots,
|
||||||
slotIndex = declSlotIndex,
|
slotIndex = declSlotIndex,
|
||||||
scopeId = declScopeId,
|
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)
|
val declaredFn = FunctionDeclStatement(spec)
|
||||||
if (isStatic && parentIsClassBody) {
|
if (isStatic && parentIsClassBody) {
|
||||||
@ -11725,6 +11801,16 @@ class Compiler(
|
|||||||
return script
|
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 {
|
suspend fun dryRun(source: Source, importManager: ImportProvider): ResolutionReport {
|
||||||
return CompileTimeResolver.dryRun(source, importManager)
|
return CompileTimeResolver.dryRun(source, importManager)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ data class FunctionDeclSpec(
|
|||||||
val slotIndex: Int?,
|
val slotIndex: Int?,
|
||||||
val scopeId: Int?,
|
val scopeId: Int?,
|
||||||
val startPos: Pos,
|
val startPos: Pos,
|
||||||
|
val resolvedMetadata: ResolvedFunctionMetadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal suspend fun executeFunctionDecl(
|
internal suspend fun executeFunctionDecl(
|
||||||
|
|||||||
@ -18,6 +18,7 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import net.sergeych.lyng.bytecode.CmdFrame
|
import net.sergeych.lyng.bytecode.CmdFrame
|
||||||
import net.sergeych.lyng.bytecode.CmdVm
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
|
import net.sergeych.lyng.bytecode.seedFrameLocalsFromScope
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjNull
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ class PropertyAccessorStatement(
|
|||||||
val bytecodeStmt = requireBytecodeBody(scope, body, "property accessor")
|
val bytecodeStmt = requireBytecodeBody(scope, body, "property accessor")
|
||||||
val fn = bytecodeStmt.bytecodeFunction()
|
val fn = bytecodeStmt.bytecodeFunction()
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||||
|
seedFrameLocalsFromScope(frame, scope)
|
||||||
val slotPlan = fn.localSlotPlanByName()
|
val slotPlan = fn.localSlotPlanByName()
|
||||||
val slotIndex = slotPlan[argName]
|
val slotIndex = slotPlan[argName]
|
||||||
?: scope.raiseIllegalState("property accessor argument $argName missing from slot plan")
|
?: 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() {
|
) : Statement() {
|
||||||
fun statements(): List<Statement> = statements
|
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.
|
* 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
|
* 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) {
|
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 declarationScope = frame.ensureScope()
|
||||||
frame.ensureScope().addExtension(
|
val property = decl.property.withDeclarationScope(declarationScope)
|
||||||
|
val type = declarationScope.resolveExtensionReceiverClass(decl.extTypeName)
|
||||||
|
declarationScope.addExtension(
|
||||||
type,
|
type,
|
||||||
decl.property.name,
|
property.name,
|
||||||
ObjRecord(
|
ObjRecord(
|
||||||
decl.property,
|
property,
|
||||||
isMutable = false,
|
isMutable = false,
|
||||||
visibility = decl.visibility,
|
visibility = decl.visibility,
|
||||||
writeVisibility = decl.setterVisibility,
|
writeVisibility = decl.setterVisibility,
|
||||||
@ -3274,9 +3276,9 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
|||||||
type = ObjRecord.Type.Property
|
type = ObjRecord.Type.Property
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val getterName = extensionPropertyGetterName(decl.extTypeName, decl.property.name)
|
val getterName = extensionPropertyGetterName(decl.extTypeName, property.name)
|
||||||
val getterWrapper = ObjExtensionPropertyGetterCallable(decl.property.name, decl.property)
|
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
|
||||||
frame.ensureScope().addItem(
|
declarationScope.addItem(
|
||||||
getterName,
|
getterName,
|
||||||
false,
|
false,
|
||||||
getterWrapper,
|
getterWrapper,
|
||||||
@ -3288,10 +3290,10 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
|||||||
if (getterLocal != null) {
|
if (getterLocal != null) {
|
||||||
frame.setObjUnchecked(frame.fn.scopeSlotCount + getterLocal, getterWrapper)
|
frame.setObjUnchecked(frame.fn.scopeSlotCount + getterLocal, getterWrapper)
|
||||||
}
|
}
|
||||||
if (decl.property.setter != null) {
|
if (property.setter != null) {
|
||||||
val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name)
|
val setterName = extensionPropertySetterName(decl.extTypeName, property.name)
|
||||||
val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property)
|
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
|
||||||
frame.ensureScope()
|
declarationScope
|
||||||
.addItem(
|
.addItem(
|
||||||
setterName,
|
setterName,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@ -56,9 +56,9 @@ internal fun trySeedFrameLocalsFromScopeFast(frame: CmdFrame, scope: Scope): Boo
|
|||||||
record.type == ObjRecord.Type.Property -> return false
|
record.type == ObjRecord.Type.Property -> return false
|
||||||
record.value is ObjProperty -> return false
|
record.value is ObjProperty -> return false
|
||||||
else -> when (val direct = record.value) {
|
else -> when (val direct = record.value) {
|
||||||
is FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
is FrameSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: return false
|
||||||
is RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
is RecordSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: return false
|
||||||
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
is ScopeSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: return false
|
||||||
else -> direct
|
else -> direct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,9 +86,9 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
|||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
when (val direct = record.value) {
|
when (val direct = record.value) {
|
||||||
is net.sergeych.lyng.FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
is net.sergeych.lyng.FrameSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: direct
|
||||||
is net.sergeych.lyng.RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
is net.sergeych.lyng.RecordSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: direct
|
||||||
is net.sergeych.lyng.ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
is net.sergeych.lyng.ScopeSlotRef -> if (record.isMutable) direct else direct.resolvedCaptureValueOrNull() ?: direct
|
||||||
else -> direct
|
else -> direct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,15 +32,21 @@ import net.sergeych.lyng.executeBytecodeWithSeed
|
|||||||
class ObjProperty(
|
class ObjProperty(
|
||||||
val name: String,
|
val name: String,
|
||||||
val getter: Obj?,
|
val getter: Obj?,
|
||||||
val setter: Obj?
|
val setter: Obj?,
|
||||||
|
private val declarationScope: Scope? = null
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
|
fun withDeclarationScope(scope: Scope): ObjProperty =
|
||||||
|
ObjProperty(name, getter, setter, scope)
|
||||||
|
|
||||||
suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): Obj {
|
suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): Obj {
|
||||||
val g = getter ?: scope.raiseError("property $name has no getter")
|
val g = getter ?: scope.raiseError("property $name has no getter")
|
||||||
// Execute getter in a child scope of the instance with 'this' properly set
|
// Execute getter in a child scope of the instance with 'this' properly set
|
||||||
// Match extension function behavior (access to instance scope + call scope).
|
// Match extension function behavior (access to instance scope + call scope).
|
||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(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
|
execScope.currentClassCtx = declaringClass
|
||||||
(g as? BytecodeCallable)?.callOnFast(execScope)?.let { return it }
|
(g as? BytecodeCallable)?.callOnFast(execScope)?.let { return it }
|
||||||
return when (g) {
|
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
|
// 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).
|
// Match extension function behavior (access to instance scope + call scope).
|
||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(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
|
execScope.currentClassCtx = declaringClass
|
||||||
(s as? BytecodeCallable)?.callOnFast(execScope)?.let { return }
|
(s as? BytecodeCallable)?.callOnFast(execScope)?.let { return }
|
||||||
when (s) {
|
when (s) {
|
||||||
|
|||||||
@ -249,4 +249,39 @@ class ScriptImportPreparationTest {
|
|||||||
val result = script.execute(manager.newStdScope()) as ObjString
|
val result = script.execute(manager.newStdScope()) as ObjString
|
||||||
assertEquals("<html><h3>Imported</h3></html>", result.value)
|
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