Add receiver-stack function types
This commit is contained in:
parent
1bababa058
commit
e107296bca
25
docs/OOP.md
25
docs/OOP.md
@ -454,6 +454,31 @@ Key rules and features:
|
||||
- For arbitrary receivers, use casts: `(expr as Type).member(...)` or `(expr as? Type)?.member(...)`.
|
||||
- Qualified access does not relax visibility.
|
||||
|
||||
### Receiver-stack lambdas
|
||||
|
||||
Qualified `this@Type` is also used outside inheritance when a lambda has multiple visible receivers.
|
||||
This is common in DSL-style builders.
|
||||
|
||||
- `A & B` means one receiver value that implements both types.
|
||||
- `context(A, B) C.()->R` means a receiver stack:
|
||||
- primary `this` is `C`
|
||||
- outer/context receivers are `A`, then `B`
|
||||
- Unqualified lookup checks the primary receiver first.
|
||||
- If the primary receiver does not define a member and several outer/context receivers do, Lyng reports a compile-time ambiguity. Use `this@Type` to select one explicitly.
|
||||
|
||||
Example:
|
||||
|
||||
```lyng
|
||||
class Html { fun title() = "html" }
|
||||
class Head { fun title() = "head" }
|
||||
class Body
|
||||
|
||||
val block: context(Html, Head) Body.()->String = {
|
||||
// title() // compile-time ambiguity: Html vs Head
|
||||
this@Html.title()
|
||||
}
|
||||
```
|
||||
|
||||
- Field inheritance (`val`/`var`) and collisions
|
||||
- Instance storage is kept per declaring class, internally disambiguated; unqualified read/write resolves to the first match in the resolution order (leftmost base).
|
||||
- Qualified read/write (via `this@Type` or casts) targets the chosen ancestor’s storage.
|
||||
|
||||
@ -164,7 +164,12 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
|
||||
- unions `A | B`
|
||||
- intersections `A & B`
|
||||
- function types `(A, B)->R` and receiver form `Receiver.(A)->R`
|
||||
- receiver-stack function types via `context(A, B) Receiver.(P)->R`
|
||||
- variadics in function type via ellipsis (`T...`)
|
||||
- `A & B` means one value implementing both types.
|
||||
- `context(A, B) Receiver.(P)->R` is different: it declares an ordered implicit-receiver stack where `Receiver` is primary `this`, then `A`, then `B`.
|
||||
- Nested receiver lambdas keep outer receivers in scope; unqualified lookup prefers the innermost receiver, and `this@Type` can select an outer/context receiver explicitly.
|
||||
- If the primary receiver does not provide a member and multiple outer/context receivers do, the lookup is a compile-time ambiguity and must be disambiguated with `this@Type`.
|
||||
- Generics:
|
||||
- type params on classes/functions/type aliases
|
||||
- bounds via `:` with union/intersection expressions
|
||||
@ -217,6 +222,7 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
|
||||
- Disambiguation helpers are supported:
|
||||
- qualified this: `this@Base.member()`
|
||||
- cast view: `(obj as Base).member()`
|
||||
- In nested receiver lambdas, `this@Type` can target any receiver visible through the receiver stack, not just inheritance ancestors.
|
||||
- On unknown receiver types, compiler allows only Object-safe members:
|
||||
- `toString`, `toInspectString`, `let`, `also`, `apply`, `run`
|
||||
- Other members require known receiver type or explicit cast.
|
||||
|
||||
@ -352,6 +352,40 @@ Sets `this` to the first argument and executes the block. Returns the value retu
|
||||
assertEquals(3, sum)
|
||||
>>> void
|
||||
|
||||
Receiver lambdas can also keep outer receivers in scope. The primary receiver wins for unqualified lookup, and `this@Type`
|
||||
selects an outer receiver explicitly:
|
||||
|
||||
class Html { fun lang() = "en" }
|
||||
class Body { fun lang() = "body" }
|
||||
|
||||
fun html(block: Html.()->String) = with(Html()) { block(this) }
|
||||
fun body(block: Body.()->String) = with(Body()) { block(this) }
|
||||
|
||||
val result = html {
|
||||
body {
|
||||
lang() + ":" + this@Html.lang()
|
||||
}
|
||||
}
|
||||
assertEquals("body:en", result)
|
||||
>>> void
|
||||
|
||||
You can declare the same requirement in a function type:
|
||||
|
||||
val block: context(Html) Body.()->String = {
|
||||
lang() + ":" + this@Html.lang()
|
||||
}
|
||||
|
||||
If the primary receiver does not define a member and multiple outer/context receivers do, Lyng reports an ambiguity instead of picking one silently:
|
||||
|
||||
class A { fun title() = "a" }
|
||||
class B { fun title() = "b" }
|
||||
class C
|
||||
|
||||
val block: context(A, B) C.()->String = {
|
||||
// title() // compile-time ambiguity
|
||||
this@A.title()
|
||||
}
|
||||
|
||||
## run
|
||||
|
||||
Executes a block after it returning the value passed by the block. for example, can be used with elvis operator:
|
||||
|
||||
@ -22,13 +22,16 @@ sealed class CodeContext {
|
||||
class Function(
|
||||
val name: String,
|
||||
val implicitThisMembers: Boolean = false,
|
||||
val implicitThisTypeName: String? = null,
|
||||
val implicitReceiverTypeNames: List<String> = emptyList(),
|
||||
val typeParams: Set<String> = emptySet(),
|
||||
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList(),
|
||||
/** True for static methods and top-level functions: they have no implicit `this`,
|
||||
* so class-body field initializers inside them should not inherit the class name. */
|
||||
val noImplicitThis: Boolean = false
|
||||
): CodeContext()
|
||||
): CodeContext() {
|
||||
val implicitThisTypeName: String?
|
||||
get() = implicitReceiverTypeNames.firstOrNull()
|
||||
}
|
||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||
var typeParams: Set<String> = emptySet()
|
||||
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||
|
||||
@ -857,32 +857,83 @@ class Compiler(
|
||||
return signature?.tailBlockReceiverType ?: if (name == "flow") "FlowBuilder" else null
|
||||
}
|
||||
|
||||
private fun currentImplicitThisTypeName(): String? {
|
||||
private fun mergeReceiverTypeNames(vararg groups: Iterable<String?>): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
for (group in groups) {
|
||||
for (typeName in group) {
|
||||
val normalized = typeName?.takeIf { it.isNotBlank() } ?: continue
|
||||
if (!result.contains(normalized)) {
|
||||
result += normalized
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun typeDeclReceiverTypeNames(typeDecl: TypeDecl.Function?): List<String> {
|
||||
if (typeDecl == null) return emptyList()
|
||||
|
||||
fun receiverTypeName(typeDecl: TypeDecl?): String? = when (typeDecl) {
|
||||
is TypeDecl.Simple -> typeDecl.name
|
||||
is TypeDecl.Generic -> typeDecl.name
|
||||
else -> null
|
||||
}
|
||||
|
||||
return mergeReceiverTypeNames(
|
||||
listOf(receiverTypeName(typeDecl.receiver)),
|
||||
typeDecl.contextReceivers.map(::receiverTypeName)
|
||||
)
|
||||
}
|
||||
|
||||
private fun currentImplicitReceiverTypeNames(): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
for (ctx in codeContexts.asReversed()) {
|
||||
when (ctx) {
|
||||
is CodeContext.Function -> {
|
||||
if (ctx.implicitThisTypeName != null) return ctx.implicitThisTypeName
|
||||
result.addAll(
|
||||
ctx.implicitReceiverTypeNames.filter { typeName ->
|
||||
typeName.isNotBlank() && !result.contains(typeName)
|
||||
}
|
||||
)
|
||||
// A static method or top-level function explicitly has no implicit `this`.
|
||||
// Stop here — do not fall through to an enclosing ClassBody.
|
||||
if (ctx.noImplicitThis) return null
|
||||
if (ctx.noImplicitThis) return result
|
||||
}
|
||||
// Class field initializers are compiled directly under ClassBody with no wrapping
|
||||
// Function. Lambdas inside those initializers must still see `this` as the class.
|
||||
is CodeContext.ClassBody -> return ctx.name
|
||||
is CodeContext.ClassBody -> {
|
||||
if (!result.contains(ctx.name)) {
|
||||
result += ctx.name
|
||||
}
|
||||
return result
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return null
|
||||
return result
|
||||
}
|
||||
|
||||
private fun implicitReceiverTypeForMember(name: String): String? {
|
||||
for (ctx in codeContexts.asReversed()) {
|
||||
val fn = ctx as? CodeContext.Function ?: continue
|
||||
if (!fn.implicitThisMembers) continue
|
||||
val typeName = fn.implicitThisTypeName ?: continue
|
||||
if (hasImplicitThisMember(name, typeName)) return typeName
|
||||
private fun currentImplicitThisTypeName(): String? {
|
||||
return currentImplicitReceiverTypeNames().firstOrNull()
|
||||
}
|
||||
|
||||
private fun implicitReceiverTypeForMember(name: String, pos: Pos? = null): String? {
|
||||
val visibleReceivers = currentImplicitReceiverTypeNames()
|
||||
if (visibleReceivers.isEmpty()) return null
|
||||
val matching = visibleReceivers.filter { hasImplicitThisMember(name, it) }
|
||||
if (matching.isEmpty()) return null
|
||||
|
||||
val primary = visibleReceivers.firstOrNull()
|
||||
if (primary != null && matching.first() == primary) {
|
||||
return primary
|
||||
}
|
||||
return null
|
||||
if (matching.size > 1 && pos != null) {
|
||||
throw ScriptError(
|
||||
pos,
|
||||
"member '$name' is ambiguous between receivers ${matching.joinToString(", ")}; use this@Type to disambiguate"
|
||||
)
|
||||
}
|
||||
return matching.first()
|
||||
}
|
||||
|
||||
private fun currentEnclosingClassName(): String? {
|
||||
@ -1151,10 +1202,11 @@ class Compiler(
|
||||
val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||
if (classCtx != null && classCtx.declaredMembers.contains(name)) {
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
val ids = resolveMemberIds(name, pos, null)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
|
||||
val implicitType = implicitReceiverTypeForMember(name, pos) ?: classCtx.name
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
|
||||
}
|
||||
val implicitTypeFromFunc = implicitReceiverTypeForMember(name)
|
||||
val implicitTypeFromFunc = implicitReceiverTypeForMember(name, pos)
|
||||
val hasImplicitClassMember = classCtx != null && hasImplicitThisMember(name, classCtx.name)
|
||||
if (implicitTypeFromFunc == null && !hasImplicitClassMember) {
|
||||
val modulePlan = moduleSlotPlan()
|
||||
@ -1277,7 +1329,7 @@ class Compiler(
|
||||
if (hasImplicitThisMember(name, implicitType)) {
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
val preferredType = if (currentImplicitThisTypeName() == null) null else implicitType
|
||||
val preferredType = if (currentImplicitReceiverTypeNames().isEmpty()) null else implicitType
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType)
|
||||
}
|
||||
}
|
||||
@ -1687,6 +1739,7 @@ class Compiler(
|
||||
)
|
||||
is TypeDecl.Function -> TypeDecl.Function(
|
||||
receiver = decl.receiver?.let { transform(it) },
|
||||
contextReceivers = decl.contextReceivers.map { transform(it) },
|
||||
params = decl.params.map { transform(it) },
|
||||
returnType = transform(decl.returnType),
|
||||
nullable = decl.isNullable
|
||||
@ -3498,13 +3551,12 @@ class Compiler(
|
||||
implicitItType: TypeDecl? = null,
|
||||
expectedCallableType: TypeDecl.Function? = null
|
||||
): ObjRef {
|
||||
fun receiverTypeName(typeDecl: TypeDecl?): String? = when (typeDecl) {
|
||||
is TypeDecl.Simple -> typeDecl.name
|
||||
is TypeDecl.Generic -> typeDecl.name
|
||||
else -> null
|
||||
}
|
||||
|
||||
val effectiveExpectedReceiverType = expectedReceiverType ?: receiverTypeName(expectedCallableType?.receiver)
|
||||
val effectiveImplicitReceiverTypes = mergeReceiverTypeNames(
|
||||
listOf(expectedReceiverType),
|
||||
typeDeclReceiverTypeNames(expectedCallableType),
|
||||
currentImplicitReceiverTypeNames()
|
||||
)
|
||||
val effectiveExpectedReceiverType = effectiveImplicitReceiverTypes.firstOrNull()
|
||||
// lambda args are different:
|
||||
val startPos = cc.currentPos()
|
||||
val label = lastLabel
|
||||
@ -3572,7 +3624,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<lambda>",
|
||||
implicitThisMembers = true,
|
||||
implicitThisTypeName = effectiveExpectedReceiverType
|
||||
implicitReceiverTypeNames = effectiveImplicitReceiverTypes
|
||||
)
|
||||
) {
|
||||
val returnLabels = label?.let { setOf(it) } ?: emptySet()
|
||||
@ -3834,6 +3886,7 @@ class Compiler(
|
||||
)
|
||||
val lambdaTypeDecl = TypeDecl.Function(
|
||||
receiver = effectiveExpectedReceiverType?.let { TypeDecl.Simple(it, false) },
|
||||
contextReceivers = effectiveImplicitReceiverTypes.drop(1).map { TypeDecl.Simple(it, false) },
|
||||
params = lambdaParamTypeDecls.toList(),
|
||||
returnType = inferredReturnDecl ?: returnClass?.let { TypeDecl.Simple(it.className, false) } ?: TypeDecl.TypeAny,
|
||||
nullable = false
|
||||
@ -4247,9 +4300,10 @@ class Compiler(
|
||||
}
|
||||
is TypeDecl.Function -> {
|
||||
val receiver = type.receiver?.let { expandTypeAliases(it, pos, seen) }
|
||||
val contextReceivers = type.contextReceivers.map { expandTypeAliases(it, pos, seen) }
|
||||
val params = type.params.map { expandTypeAliases(it, pos, seen) }
|
||||
val ret = expandTypeAliases(type.returnType, pos, seen)
|
||||
TypeDecl.Function(receiver, params, ret, type.nullable)
|
||||
TypeDecl.Function(receiver, contextReceivers, params, ret, type.nullable)
|
||||
}
|
||||
is TypeDecl.Ellipsis -> {
|
||||
val elem = expandTypeAliases(type.elementType, pos, seen)
|
||||
@ -4336,9 +4390,10 @@ class Compiler(
|
||||
}
|
||||
is TypeDecl.Function -> {
|
||||
val receiver = type.receiver?.let { substituteTypeAliasTypeVars(it, bindings) }
|
||||
val contextReceivers = type.contextReceivers.map { substituteTypeAliasTypeVars(it, bindings) }
|
||||
val params = type.params.map { substituteTypeAliasTypeVars(it, bindings) }
|
||||
val ret = substituteTypeAliasTypeVars(type.returnType, bindings)
|
||||
TypeDecl.Function(receiver, params, ret, type.nullable)
|
||||
TypeDecl.Function(receiver, contextReceivers, params, ret, type.nullable)
|
||||
}
|
||||
is TypeDecl.Ellipsis -> {
|
||||
val elem = substituteTypeAliasTypeVars(type.elementType, bindings)
|
||||
@ -4480,6 +4535,35 @@ class Compiler(
|
||||
|
||||
var receiverDecl: TypeDecl? = null
|
||||
var receiverMini: MiniTypeRef? = null
|
||||
val contextReceivers = mutableListOf<Pair<TypeDecl, MiniTypeRef>>()
|
||||
|
||||
run {
|
||||
val contextSaved = cc.savePos()
|
||||
val contextToken = cc.peekNextNonWhitespace()
|
||||
if (contextToken.type != Token.Type.ID || contextToken.value != "context") {
|
||||
return@run
|
||||
}
|
||||
cc.nextNonWhitespace()
|
||||
if (!cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||
cc.restorePos(contextSaved)
|
||||
return@run
|
||||
}
|
||||
cc.skipWsTokens()
|
||||
if (cc.peekNextNonWhitespace().type != Token.Type.RPAREN) {
|
||||
while (true) {
|
||||
val parsed = parseTypeExpressionWithMini()
|
||||
contextReceivers += parsed
|
||||
val sep = cc.nextNonWhitespace()
|
||||
when (sep.type) {
|
||||
Token.Type.COMMA -> continue
|
||||
Token.Type.RPAREN -> break
|
||||
else -> sep.raiseSyntax("expected ',' or ')' in context receiver list")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cc.nextNonWhitespace()
|
||||
}
|
||||
}
|
||||
|
||||
val first = cc.peekNextNonWhitespace()
|
||||
if (first.type == Token.Type.LPAREN) {
|
||||
@ -4524,12 +4608,14 @@ class Compiler(
|
||||
val mini = MiniFunctionType(
|
||||
range = MiniRange(rangeStart, rangeEnd),
|
||||
receiver = normalizedReceiverMini,
|
||||
contextReceivers = contextReceivers.map { it.second },
|
||||
params = params.map { it.second },
|
||||
returnType = retMini,
|
||||
nullable = isNullable
|
||||
)
|
||||
val sem = TypeDecl.Function(
|
||||
receiver = normalizedReceiverDecl,
|
||||
contextReceivers = contextReceivers.map { it.first },
|
||||
params = params.map { it.first },
|
||||
returnType = retDecl,
|
||||
nullable = isNullable
|
||||
@ -5100,7 +5186,17 @@ class Compiler(
|
||||
TypeDecl.TypeNullableAny -> "Any?"
|
||||
is TypeDecl.Simple -> "S:${type.name}"
|
||||
is TypeDecl.Generic -> "G:${type.name}<${type.args.joinToString(",") { typeDeclKey(it) }}>"
|
||||
is TypeDecl.Function -> "F:(${type.params.joinToString(",") { typeDeclKey(it) }})->${typeDeclKey(type.returnType)}"
|
||||
is TypeDecl.Function -> buildString {
|
||||
append("F:")
|
||||
type.receiver?.let { append("recv=").append(typeDeclKey(it)).append(";") }
|
||||
if (type.contextReceivers.isNotEmpty()) {
|
||||
append("ctx=").append(type.contextReceivers.joinToString(",") { typeDeclKey(it) }).append(";")
|
||||
}
|
||||
append('(')
|
||||
append(type.params.joinToString(",") { typeDeclKey(it) })
|
||||
append(")->")
|
||||
append(typeDeclKey(type.returnType))
|
||||
}
|
||||
is TypeDecl.Ellipsis -> "E:${typeDeclKey(type.elementType)}"
|
||||
is TypeDecl.TypeVar -> "V:${type.name}"
|
||||
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
|
||||
@ -5793,6 +5889,14 @@ class Compiler(
|
||||
memberType.receiver?.let { declaredReceiver ->
|
||||
receiverDecl?.let { collectTypeVarBindings(declaredReceiver, it, bindings) }
|
||||
}
|
||||
val contextLimit = minOf(memberType.contextReceivers.size, currentImplicitReceiverTypeNames().size)
|
||||
for (i in 0 until contextLimit) {
|
||||
collectTypeVarBindings(
|
||||
memberType.contextReceivers[i],
|
||||
TypeDecl.Simple(currentImplicitReceiverTypeNames()[i], false),
|
||||
bindings
|
||||
)
|
||||
}
|
||||
|
||||
val paramList = memberType.params
|
||||
val ellipsisIndex = paramList.indexOfFirst { it is TypeDecl.Ellipsis }
|
||||
@ -5842,6 +5946,7 @@ class Compiler(
|
||||
if (explicitTypeArgs.isNullOrEmpty()) return
|
||||
val typeVars = LinkedHashSet<String>()
|
||||
memberType.receiver?.let { collectTypeVarNamesInOrder(it, typeVars) }
|
||||
memberType.contextReceivers.forEach { collectTypeVarNamesInOrder(it, typeVars) }
|
||||
memberType.params.forEach { collectTypeVarNamesInOrder(it, typeVars) }
|
||||
collectTypeVarNamesInOrder(memberType.returnType, typeVars)
|
||||
val names = typeVars.toList()
|
||||
@ -5857,6 +5962,7 @@ class Compiler(
|
||||
is TypeDecl.Generic -> type.args.forEach { collectTypeVarNamesInOrder(it, out) }
|
||||
is TypeDecl.Function -> {
|
||||
type.receiver?.let { collectTypeVarNamesInOrder(it, out) }
|
||||
type.contextReceivers.forEach { collectTypeVarNamesInOrder(it, out) }
|
||||
type.params.forEach { collectTypeVarNamesInOrder(it, out) }
|
||||
collectTypeVarNamesInOrder(type.returnType, out)
|
||||
}
|
||||
@ -6721,10 +6827,17 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
is TypeDecl.Function -> {
|
||||
if (argType is TypeDecl.Function && paramType.params.size == argType.params.size) {
|
||||
if (
|
||||
argType is TypeDecl.Function &&
|
||||
paramType.params.size == argType.params.size &&
|
||||
paramType.contextReceivers.size == argType.contextReceivers.size
|
||||
) {
|
||||
if (paramType.receiver != null && argType.receiver != null) {
|
||||
collectTypeVarBindings(paramType.receiver, argType.receiver, out)
|
||||
}
|
||||
for (i in paramType.contextReceivers.indices) {
|
||||
collectTypeVarBindings(paramType.contextReceivers[i], argType.contextReceivers[i], out)
|
||||
}
|
||||
for (i in paramType.params.indices) {
|
||||
collectTypeVarBindings(paramType.params[i], argType.params[i], out)
|
||||
}
|
||||
@ -7207,7 +7320,7 @@ class Compiler(
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
if ((classContext || implicitThis) && extensionNames.contains(left.name)) {
|
||||
val receiverTypeName = implicitReceiverTypeForMember(left.name) ?: implicitThisTypeName
|
||||
val receiverTypeName = implicitReceiverTypeForMember(left.name, left.pos()) ?: implicitThisTypeName
|
||||
val ids = resolveImplicitThisMemberIds(left.name, left.pos(), receiverTypeName)
|
||||
ImplicitThisMethodCallRef(
|
||||
left.name,
|
||||
@ -7233,7 +7346,7 @@ class Compiler(
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
if ((classContext || implicitThis) && extensionNames.contains(left.name)) {
|
||||
val receiverTypeName = implicitReceiverTypeForMember(left.name) ?: implicitThisTypeName
|
||||
val receiverTypeName = implicitReceiverTypeForMember(left.name, left.pos()) ?: implicitThisTypeName
|
||||
val ids = resolveImplicitThisMemberIds(left.name, left.pos(), receiverTypeName)
|
||||
ImplicitThisMethodCallRef(
|
||||
left.name,
|
||||
@ -7284,6 +7397,16 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun expectedCallableArgumentType(target: ObjRef, argIndex: Int): TypeDecl.Function? {
|
||||
lookupNamedFunctionDecl(target)?.let { decl ->
|
||||
val params = decl.params.map { param ->
|
||||
if (param.isEllipsis) TypeDecl.Ellipsis(param.type) else param.type
|
||||
}
|
||||
if (argIndex < params.size) {
|
||||
return params[argIndex] as? TypeDecl.Function
|
||||
}
|
||||
val ellipsis = params.lastOrNull() as? TypeDecl.Ellipsis
|
||||
return ellipsis?.elementType as? TypeDecl.Function
|
||||
}
|
||||
val decl = (resolveReceiverTypeDecl(target) ?: seedTypeDeclFromRef(target)) as? TypeDecl.Function
|
||||
?: return null
|
||||
val params = when (target) {
|
||||
@ -7609,7 +7732,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<init>",
|
||||
implicitThisMembers = true,
|
||||
implicitThisTypeName = implicitThisType
|
||||
implicitReceiverTypeNames = listOfNotNull(implicitThisType)
|
||||
)
|
||||
) {
|
||||
parseBlock()
|
||||
@ -9728,7 +9851,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
name,
|
||||
implicitThisMembers = implicitThisMembers,
|
||||
implicitThisTypeName = implicitThisTypeName,
|
||||
implicitReceiverTypeNames = listOfNotNull(implicitThisTypeName),
|
||||
typeParams = typeParams,
|
||||
typeParamDecls = typeParamDecls,
|
||||
noImplicitThis = noImplicitThis
|
||||
@ -10843,9 +10966,17 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
val initialExpression = if (setNull || isProperty) null
|
||||
else parseStatement(true)
|
||||
?: throw ScriptError(effectiveEqToken!!.pos, "Expected initializer expression")
|
||||
val initialExpression = if (setNull || isProperty) {
|
||||
null
|
||||
} else if (varTypeDecl is TypeDecl.Function && cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
val brace = cc.nextNonWhitespace()
|
||||
ExpressionStatement(
|
||||
parseLambdaExpression(expectedCallableType = varTypeDecl),
|
||||
brace.pos
|
||||
)
|
||||
} else {
|
||||
parseStatement(true) ?: throw ScriptError(effectiveEqToken!!.pos, "Expected initializer expression")
|
||||
}
|
||||
|
||||
if (varTypeDecl == TypeDecl.TypeAny && initialExpression != null) {
|
||||
val inferred = inferTypeDeclFromInitializer(initialExpression)
|
||||
@ -11071,7 +11202,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<getter>",
|
||||
implicitThisMembers = accessorImplicitThisMembers,
|
||||
implicitThisTypeName = accessorImplicitThisTypeName
|
||||
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||
)
|
||||
) {
|
||||
wrapFunctionBytecode(parseBlock(), "<getter>")
|
||||
@ -11083,7 +11214,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<getter>",
|
||||
implicitThisMembers = accessorImplicitThisMembers,
|
||||
implicitThisTypeName = accessorImplicitThisTypeName
|
||||
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||
)
|
||||
) {
|
||||
val expr = parseExpression()
|
||||
@ -11110,7 +11241,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<setter>",
|
||||
implicitThisMembers = accessorImplicitThisMembers,
|
||||
implicitThisTypeName = accessorImplicitThisTypeName
|
||||
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||
)
|
||||
) {
|
||||
wrapFunctionBytecode(parseBlockWithPredeclared(listOf(setArgName to true)), "<setter>")
|
||||
@ -11123,7 +11254,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<setter>",
|
||||
implicitThisMembers = accessorImplicitThisMembers,
|
||||
implicitThisTypeName = accessorImplicitThisTypeName
|
||||
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||
)
|
||||
) {
|
||||
parseExpressionBlockWithPredeclared(listOf(setArgName to true))
|
||||
@ -11152,7 +11283,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<setter>",
|
||||
implicitThisMembers = accessorImplicitThisMembers,
|
||||
implicitThisTypeName = accessorImplicitThisTypeName
|
||||
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||
)
|
||||
) {
|
||||
parseBlockWithPredeclared(listOf(setArg.value to true))
|
||||
@ -11166,7 +11297,7 @@ class Compiler(
|
||||
CodeContext.Function(
|
||||
"<setter>",
|
||||
implicitThisMembers = accessorImplicitThisMembers,
|
||||
implicitThisTypeName = accessorImplicitThisTypeName
|
||||
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||
)
|
||||
) {
|
||||
parseExpressionBlockWithPredeclared(listOf(setArg.value to true))
|
||||
|
||||
@ -26,6 +26,7 @@ sealed class TypeDecl(val isNullable:Boolean = false) {
|
||||
// ??
|
||||
data class Function(
|
||||
val receiver: TypeDecl?,
|
||||
val contextReceivers: List<TypeDecl> = emptyList(),
|
||||
val params: List<TypeDecl>,
|
||||
val returnType: TypeDecl,
|
||||
val nullable: Boolean = false
|
||||
|
||||
@ -36,6 +36,7 @@ data class TypeNameDoc(val segments: List<String>, override val nullable: Boolea
|
||||
data class TypeGenericDoc(val base: TypeNameDoc, val args: List<TypeDoc>, override val nullable: Boolean = false) : TypeDoc
|
||||
data class TypeFunctionDoc(
|
||||
val receiver: TypeDoc? = null,
|
||||
val contextReceivers: List<TypeDoc> = emptyList(),
|
||||
val params: List<TypeDoc>,
|
||||
val returns: TypeDoc,
|
||||
override val nullable: Boolean = false
|
||||
@ -45,8 +46,13 @@ data class TypeVarDoc(val name: String, override val nullable: Boolean = false)
|
||||
// Convenience builders
|
||||
fun type(name: String, nullable: Boolean = false) = TypeNameDoc(name.split('.'), nullable)
|
||||
fun typeVar(name: String, nullable: Boolean = false) = TypeVarDoc(name, nullable)
|
||||
fun funType(params: List<TypeDoc>, returns: TypeDoc, receiver: TypeDoc? = null, nullable: Boolean = false) =
|
||||
TypeFunctionDoc(receiver, params, returns, nullable)
|
||||
fun funType(
|
||||
params: List<TypeDoc>,
|
||||
returns: TypeDoc,
|
||||
receiver: TypeDoc? = null,
|
||||
contextReceivers: List<TypeDoc> = emptyList(),
|
||||
nullable: Boolean = false
|
||||
) = TypeFunctionDoc(receiver, contextReceivers, params, returns, nullable)
|
||||
|
||||
// ---------------- Registry ----------------
|
||||
|
||||
@ -281,6 +287,7 @@ internal fun TypeDoc.toMiniTypeRef(): MiniTypeRef = when (this) {
|
||||
is TypeFunctionDoc -> MiniFunctionType(
|
||||
range = builtinRange(),
|
||||
receiver = this.receiver?.toMiniTypeRef(),
|
||||
contextReceivers = this.contextReceivers.map { it.toMiniTypeRef() },
|
||||
params = this.params.map { it.toMiniTypeRef() },
|
||||
returnType = this.returns.toMiniTypeRef(),
|
||||
nullable = this.nullable
|
||||
|
||||
@ -139,6 +139,7 @@ data class MiniGenericType(
|
||||
data class MiniFunctionType(
|
||||
override val range: MiniRange,
|
||||
val receiver: MiniTypeRef?,
|
||||
val contextReceivers: List<MiniTypeRef>,
|
||||
val params: List<MiniTypeRef>,
|
||||
val returnType: MiniTypeRef,
|
||||
val nullable: Boolean
|
||||
|
||||
@ -206,7 +206,17 @@ private fun typeDeclKey(type: TypeDecl): String = when (type) {
|
||||
TypeDecl.TypeNullableAny -> "Any?"
|
||||
is TypeDecl.Simple -> "S:${type.name}"
|
||||
is TypeDecl.Generic -> "G:${type.name}<${type.args.joinToString(",") { typeDeclKey(it) }}>"
|
||||
is TypeDecl.Function -> "F:(${type.params.joinToString(",") { typeDeclKey(it) }})->${typeDeclKey(type.returnType)}"
|
||||
is TypeDecl.Function -> buildString {
|
||||
append("F:")
|
||||
type.receiver?.let { append("recv=").append(typeDeclKey(it)).append(";") }
|
||||
if (type.contextReceivers.isNotEmpty()) {
|
||||
append("ctx=").append(type.contextReceivers.joinToString(",") { typeDeclKey(it) }).append(";")
|
||||
}
|
||||
append('(')
|
||||
append(type.params.joinToString(",") { typeDeclKey(it) })
|
||||
append(")->")
|
||||
append(typeDeclKey(type.returnType))
|
||||
}
|
||||
is TypeDecl.Ellipsis -> "E:${typeDeclKey(type.elementType)}"
|
||||
is TypeDecl.TypeVar -> "V:${type.name}"
|
||||
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
|
||||
|
||||
@ -473,6 +473,29 @@ class MiniAstTest {
|
||||
assertEquals("b", fn.params[1].name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun miniAst_captures_context_receiver_function_type() = runTest {
|
||||
val code = """
|
||||
val block: context(Html, Head) Body.()->String = { "ok" }
|
||||
""".trimIndent()
|
||||
val (_, sink) = compileWithMini(code)
|
||||
val mini = sink.build()
|
||||
assertNotNull(mini)
|
||||
val vd = mini.declarations.filterIsInstance<MiniValDecl>().firstOrNull { it.name == "block" }
|
||||
assertNotNull(vd)
|
||||
val type = vd.type as MiniFunctionType
|
||||
val receiver = type.receiver as MiniTypeName
|
||||
assertEquals(listOf("Body"), receiver.segments.map { it.name })
|
||||
assertEquals(2, type.contextReceivers.size)
|
||||
val ctx0 = type.contextReceivers[0] as MiniTypeName
|
||||
val ctx1 = type.contextReceivers[1] as MiniTypeName
|
||||
assertEquals(listOf("Html"), ctx0.segments.map { it.name })
|
||||
assertEquals(listOf("Head"), ctx1.segments.map { it.name })
|
||||
assertTrue(type.params.isEmpty())
|
||||
val ret = type.returnType as MiniTypeName
|
||||
assertEquals(listOf("String"), ret.segments.map { it.name })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun miniAst_captures_dokka_tags() = runTest {
|
||||
val code = """
|
||||
|
||||
@ -154,4 +154,132 @@ class OptTest {
|
||||
assert( cnt == 4 )
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReceivers1() = runTest {
|
||||
eval("""
|
||||
class RA {
|
||||
fun a() { println("a") }
|
||||
}
|
||||
class RB {
|
||||
fun b() { println("b") }
|
||||
}
|
||||
|
||||
fun ta( f: RA.()->Unit ) {
|
||||
val instance = RA()
|
||||
with(instance) { f(this) }
|
||||
}
|
||||
fun tb( f: RB.()->Unit ) {
|
||||
val b = RB()
|
||||
with(b) { f(this) }
|
||||
}
|
||||
ta {
|
||||
a()
|
||||
tb {
|
||||
b()
|
||||
// but important: a() must still be accessible
|
||||
// because it is inner block, sort of closure:
|
||||
a()
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testContextReceiverFunctionType() = runTest {
|
||||
eval("""
|
||||
class RA {
|
||||
fun value(): Int = 10
|
||||
}
|
||||
class RB {
|
||||
fun value(): Int = 20
|
||||
}
|
||||
|
||||
fun ta(f: RA.()->Int): Int {
|
||||
val instance = RA()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
|
||||
fun tb(f: context(RA) RB.()->Int): Int {
|
||||
val instance = RB()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
|
||||
val result = ta {
|
||||
val block: context(RA) RB.()->Int = {
|
||||
value() + this@RA.value()
|
||||
}
|
||||
tb(block)
|
||||
}
|
||||
|
||||
assertEquals(30, result)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNestedReceiverQualifiedThis() = runTest {
|
||||
eval("""
|
||||
class RA {
|
||||
fun value(): Int = 1
|
||||
}
|
||||
class RB {
|
||||
fun value(): Int = 2
|
||||
}
|
||||
|
||||
fun ta(f: RA.()->Int): Int {
|
||||
val instance = RA()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
fun tb(f: RB.()->Int): Int {
|
||||
val instance = RB()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
|
||||
val result = ta {
|
||||
tb {
|
||||
value() + this@RA.value()
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(3, result)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReceiverAmbiguityRequiresQualifiedThis() = runTest {
|
||||
val ex = assertFailsWith<ScriptError> {
|
||||
eval("""
|
||||
class RA {
|
||||
fun shared(): Int = 10
|
||||
}
|
||||
class RC {
|
||||
fun shared(): Int = 30
|
||||
}
|
||||
class RB
|
||||
|
||||
fun ta(f: RA.()->Int): Int {
|
||||
val instance = RA()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
fun tc(f: RC.()->Int): Int {
|
||||
val instance = RC()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
fun tb(f: context(RA, RC) RB.()->Int): Int {
|
||||
val instance = RB()
|
||||
return with(instance) { f(this) }
|
||||
}
|
||||
|
||||
ta {
|
||||
tc {
|
||||
val block: context(RA, RC) RB.()->Int = {
|
||||
shared()
|
||||
}
|
||||
tb(block)
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
assertContains(ex.message ?: "", "ambiguous between receivers RA, RC")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user