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(...)`.
|
- For arbitrary receivers, use casts: `(expr as Type).member(...)` or `(expr as? Type)?.member(...)`.
|
||||||
- Qualified access does not relax visibility.
|
- 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
|
- 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).
|
- 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.
|
- 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`
|
- unions `A | B`
|
||||||
- intersections `A & B`
|
- intersections `A & B`
|
||||||
- function types `(A, B)->R` and receiver form `Receiver.(A)->R`
|
- 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...`)
|
- 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:
|
- Generics:
|
||||||
- type params on classes/functions/type aliases
|
- type params on classes/functions/type aliases
|
||||||
- bounds via `:` with union/intersection expressions
|
- 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:
|
- Disambiguation helpers are supported:
|
||||||
- qualified this: `this@Base.member()`
|
- qualified this: `this@Base.member()`
|
||||||
- cast view: `(obj as 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:
|
- On unknown receiver types, compiler allows only Object-safe members:
|
||||||
- `toString`, `toInspectString`, `let`, `also`, `apply`, `run`
|
- `toString`, `toInspectString`, `let`, `also`, `apply`, `run`
|
||||||
- Other members require known receiver type or explicit cast.
|
- 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)
|
assertEquals(3, sum)
|
||||||
>>> void
|
>>> 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
|
## run
|
||||||
|
|
||||||
Executes a block after it returning the value passed by the block. for example, can be used with elvis operator:
|
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(
|
class Function(
|
||||||
val name: String,
|
val name: String,
|
||||||
val implicitThisMembers: Boolean = false,
|
val implicitThisMembers: Boolean = false,
|
||||||
val implicitThisTypeName: String? = null,
|
val implicitReceiverTypeNames: List<String> = emptyList(),
|
||||||
val typeParams: Set<String> = emptySet(),
|
val typeParams: Set<String> = emptySet(),
|
||||||
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList(),
|
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList(),
|
||||||
/** True for static methods and top-level functions: they have no implicit `this`,
|
/** 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. */
|
* so class-body field initializers inside them should not inherit the class name. */
|
||||||
val noImplicitThis: Boolean = false
|
val noImplicitThis: Boolean = false
|
||||||
): CodeContext()
|
): CodeContext() {
|
||||||
|
val implicitThisTypeName: String?
|
||||||
|
get() = implicitReceiverTypeNames.firstOrNull()
|
||||||
|
}
|
||||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||||
var typeParams: Set<String> = emptySet()
|
var typeParams: Set<String> = emptySet()
|
||||||
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||||
|
|||||||
@ -857,32 +857,83 @@ class Compiler(
|
|||||||
return signature?.tailBlockReceiverType ?: if (name == "flow") "FlowBuilder" else null
|
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()) {
|
for (ctx in codeContexts.asReversed()) {
|
||||||
when (ctx) {
|
when (ctx) {
|
||||||
is CodeContext.Function -> {
|
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`.
|
// A static method or top-level function explicitly has no implicit `this`.
|
||||||
// Stop here — do not fall through to an enclosing ClassBody.
|
// 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
|
// Class field initializers are compiled directly under ClassBody with no wrapping
|
||||||
// Function. Lambdas inside those initializers must still see `this` as the class.
|
// 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 -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun implicitReceiverTypeForMember(name: String): String? {
|
private fun currentImplicitThisTypeName(): String? {
|
||||||
for (ctx in codeContexts.asReversed()) {
|
return currentImplicitReceiverTypeNames().firstOrNull()
|
||||||
val fn = ctx as? CodeContext.Function ?: continue
|
}
|
||||||
if (!fn.implicitThisMembers) continue
|
|
||||||
val typeName = fn.implicitThisTypeName ?: continue
|
private fun implicitReceiverTypeForMember(name: String, pos: Pos? = null): String? {
|
||||||
if (hasImplicitThisMember(name, typeName)) return typeName
|
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? {
|
private fun currentEnclosingClassName(): String? {
|
||||||
@ -1151,10 +1202,11 @@ class Compiler(
|
|||||||
val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||||
if (classCtx != null && classCtx.declaredMembers.contains(name)) {
|
if (classCtx != null && classCtx.declaredMembers.contains(name)) {
|
||||||
resolutionSink?.referenceMember(name, pos)
|
resolutionSink?.referenceMember(name, pos)
|
||||||
val ids = resolveMemberIds(name, pos, null)
|
val implicitType = implicitReceiverTypeForMember(name, pos) ?: classCtx.name
|
||||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
|
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)
|
val hasImplicitClassMember = classCtx != null && hasImplicitThisMember(name, classCtx.name)
|
||||||
if (implicitTypeFromFunc == null && !hasImplicitClassMember) {
|
if (implicitTypeFromFunc == null && !hasImplicitClassMember) {
|
||||||
val modulePlan = moduleSlotPlan()
|
val modulePlan = moduleSlotPlan()
|
||||||
@ -1277,7 +1329,7 @@ class Compiler(
|
|||||||
if (hasImplicitThisMember(name, implicitType)) {
|
if (hasImplicitThisMember(name, implicitType)) {
|
||||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||||
val ids = resolveImplicitThisMemberIds(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)
|
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1687,6 +1739,7 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
is TypeDecl.Function -> TypeDecl.Function(
|
is TypeDecl.Function -> TypeDecl.Function(
|
||||||
receiver = decl.receiver?.let { transform(it) },
|
receiver = decl.receiver?.let { transform(it) },
|
||||||
|
contextReceivers = decl.contextReceivers.map { transform(it) },
|
||||||
params = decl.params.map { transform(it) },
|
params = decl.params.map { transform(it) },
|
||||||
returnType = transform(decl.returnType),
|
returnType = transform(decl.returnType),
|
||||||
nullable = decl.isNullable
|
nullable = decl.isNullable
|
||||||
@ -3498,13 +3551,12 @@ class Compiler(
|
|||||||
implicitItType: TypeDecl? = null,
|
implicitItType: TypeDecl? = null,
|
||||||
expectedCallableType: TypeDecl.Function? = null
|
expectedCallableType: TypeDecl.Function? = null
|
||||||
): ObjRef {
|
): ObjRef {
|
||||||
fun receiverTypeName(typeDecl: TypeDecl?): String? = when (typeDecl) {
|
val effectiveImplicitReceiverTypes = mergeReceiverTypeNames(
|
||||||
is TypeDecl.Simple -> typeDecl.name
|
listOf(expectedReceiverType),
|
||||||
is TypeDecl.Generic -> typeDecl.name
|
typeDeclReceiverTypeNames(expectedCallableType),
|
||||||
else -> null
|
currentImplicitReceiverTypeNames()
|
||||||
}
|
)
|
||||||
|
val effectiveExpectedReceiverType = effectiveImplicitReceiverTypes.firstOrNull()
|
||||||
val effectiveExpectedReceiverType = expectedReceiverType ?: receiverTypeName(expectedCallableType?.receiver)
|
|
||||||
// lambda args are different:
|
// lambda args are different:
|
||||||
val startPos = cc.currentPos()
|
val startPos = cc.currentPos()
|
||||||
val label = lastLabel
|
val label = lastLabel
|
||||||
@ -3572,7 +3624,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<lambda>",
|
"<lambda>",
|
||||||
implicitThisMembers = true,
|
implicitThisMembers = true,
|
||||||
implicitThisTypeName = effectiveExpectedReceiverType
|
implicitReceiverTypeNames = effectiveImplicitReceiverTypes
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val returnLabels = label?.let { setOf(it) } ?: emptySet()
|
val returnLabels = label?.let { setOf(it) } ?: emptySet()
|
||||||
@ -3834,6 +3886,7 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
val lambdaTypeDecl = TypeDecl.Function(
|
val lambdaTypeDecl = TypeDecl.Function(
|
||||||
receiver = effectiveExpectedReceiverType?.let { TypeDecl.Simple(it, false) },
|
receiver = effectiveExpectedReceiverType?.let { TypeDecl.Simple(it, false) },
|
||||||
|
contextReceivers = effectiveImplicitReceiverTypes.drop(1).map { TypeDecl.Simple(it, false) },
|
||||||
params = lambdaParamTypeDecls.toList(),
|
params = lambdaParamTypeDecls.toList(),
|
||||||
returnType = inferredReturnDecl ?: returnClass?.let { TypeDecl.Simple(it.className, false) } ?: TypeDecl.TypeAny,
|
returnType = inferredReturnDecl ?: returnClass?.let { TypeDecl.Simple(it.className, false) } ?: TypeDecl.TypeAny,
|
||||||
nullable = false
|
nullable = false
|
||||||
@ -4247,9 +4300,10 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
is TypeDecl.Function -> {
|
is TypeDecl.Function -> {
|
||||||
val receiver = type.receiver?.let { expandTypeAliases(it, pos, seen) }
|
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 params = type.params.map { expandTypeAliases(it, pos, seen) }
|
||||||
val ret = expandTypeAliases(type.returnType, 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 -> {
|
is TypeDecl.Ellipsis -> {
|
||||||
val elem = expandTypeAliases(type.elementType, pos, seen)
|
val elem = expandTypeAliases(type.elementType, pos, seen)
|
||||||
@ -4336,9 +4390,10 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
is TypeDecl.Function -> {
|
is TypeDecl.Function -> {
|
||||||
val receiver = type.receiver?.let { substituteTypeAliasTypeVars(it, bindings) }
|
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 params = type.params.map { substituteTypeAliasTypeVars(it, bindings) }
|
||||||
val ret = substituteTypeAliasTypeVars(type.returnType, 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 -> {
|
is TypeDecl.Ellipsis -> {
|
||||||
val elem = substituteTypeAliasTypeVars(type.elementType, bindings)
|
val elem = substituteTypeAliasTypeVars(type.elementType, bindings)
|
||||||
@ -4480,6 +4535,35 @@ class Compiler(
|
|||||||
|
|
||||||
var receiverDecl: TypeDecl? = null
|
var receiverDecl: TypeDecl? = null
|
||||||
var receiverMini: MiniTypeRef? = 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()
|
val first = cc.peekNextNonWhitespace()
|
||||||
if (first.type == Token.Type.LPAREN) {
|
if (first.type == Token.Type.LPAREN) {
|
||||||
@ -4524,12 +4608,14 @@ class Compiler(
|
|||||||
val mini = MiniFunctionType(
|
val mini = MiniFunctionType(
|
||||||
range = MiniRange(rangeStart, rangeEnd),
|
range = MiniRange(rangeStart, rangeEnd),
|
||||||
receiver = normalizedReceiverMini,
|
receiver = normalizedReceiverMini,
|
||||||
|
contextReceivers = contextReceivers.map { it.second },
|
||||||
params = params.map { it.second },
|
params = params.map { it.second },
|
||||||
returnType = retMini,
|
returnType = retMini,
|
||||||
nullable = isNullable
|
nullable = isNullable
|
||||||
)
|
)
|
||||||
val sem = TypeDecl.Function(
|
val sem = TypeDecl.Function(
|
||||||
receiver = normalizedReceiverDecl,
|
receiver = normalizedReceiverDecl,
|
||||||
|
contextReceivers = contextReceivers.map { it.first },
|
||||||
params = params.map { it.first },
|
params = params.map { it.first },
|
||||||
returnType = retDecl,
|
returnType = retDecl,
|
||||||
nullable = isNullable
|
nullable = isNullable
|
||||||
@ -5100,7 +5186,17 @@ class Compiler(
|
|||||||
TypeDecl.TypeNullableAny -> "Any?"
|
TypeDecl.TypeNullableAny -> "Any?"
|
||||||
is TypeDecl.Simple -> "S:${type.name}"
|
is TypeDecl.Simple -> "S:${type.name}"
|
||||||
is TypeDecl.Generic -> "G:${type.name}<${type.args.joinToString(",") { typeDeclKey(it) }}>"
|
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.Ellipsis -> "E:${typeDeclKey(type.elementType)}"
|
||||||
is TypeDecl.TypeVar -> "V:${type.name}"
|
is TypeDecl.TypeVar -> "V:${type.name}"
|
||||||
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
|
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
|
||||||
@ -5793,6 +5889,14 @@ class Compiler(
|
|||||||
memberType.receiver?.let { declaredReceiver ->
|
memberType.receiver?.let { declaredReceiver ->
|
||||||
receiverDecl?.let { collectTypeVarBindings(declaredReceiver, it, bindings) }
|
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 paramList = memberType.params
|
||||||
val ellipsisIndex = paramList.indexOfFirst { it is TypeDecl.Ellipsis }
|
val ellipsisIndex = paramList.indexOfFirst { it is TypeDecl.Ellipsis }
|
||||||
@ -5842,6 +5946,7 @@ class Compiler(
|
|||||||
if (explicitTypeArgs.isNullOrEmpty()) return
|
if (explicitTypeArgs.isNullOrEmpty()) return
|
||||||
val typeVars = LinkedHashSet<String>()
|
val typeVars = LinkedHashSet<String>()
|
||||||
memberType.receiver?.let { collectTypeVarNamesInOrder(it, typeVars) }
|
memberType.receiver?.let { collectTypeVarNamesInOrder(it, typeVars) }
|
||||||
|
memberType.contextReceivers.forEach { collectTypeVarNamesInOrder(it, typeVars) }
|
||||||
memberType.params.forEach { collectTypeVarNamesInOrder(it, typeVars) }
|
memberType.params.forEach { collectTypeVarNamesInOrder(it, typeVars) }
|
||||||
collectTypeVarNamesInOrder(memberType.returnType, typeVars)
|
collectTypeVarNamesInOrder(memberType.returnType, typeVars)
|
||||||
val names = typeVars.toList()
|
val names = typeVars.toList()
|
||||||
@ -5857,6 +5962,7 @@ class Compiler(
|
|||||||
is TypeDecl.Generic -> type.args.forEach { collectTypeVarNamesInOrder(it, out) }
|
is TypeDecl.Generic -> type.args.forEach { collectTypeVarNamesInOrder(it, out) }
|
||||||
is TypeDecl.Function -> {
|
is TypeDecl.Function -> {
|
||||||
type.receiver?.let { collectTypeVarNamesInOrder(it, out) }
|
type.receiver?.let { collectTypeVarNamesInOrder(it, out) }
|
||||||
|
type.contextReceivers.forEach { collectTypeVarNamesInOrder(it, out) }
|
||||||
type.params.forEach { collectTypeVarNamesInOrder(it, out) }
|
type.params.forEach { collectTypeVarNamesInOrder(it, out) }
|
||||||
collectTypeVarNamesInOrder(type.returnType, out)
|
collectTypeVarNamesInOrder(type.returnType, out)
|
||||||
}
|
}
|
||||||
@ -6721,10 +6827,17 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TypeDecl.Function -> {
|
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) {
|
if (paramType.receiver != null && argType.receiver != null) {
|
||||||
collectTypeVarBindings(paramType.receiver, argType.receiver, out)
|
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) {
|
for (i in paramType.params.indices) {
|
||||||
collectTypeVarBindings(paramType.params[i], argType.params[i], out)
|
collectTypeVarBindings(paramType.params[i], argType.params[i], out)
|
||||||
}
|
}
|
||||||
@ -7207,7 +7320,7 @@ class Compiler(
|
|||||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||||
}
|
}
|
||||||
if ((classContext || implicitThis) && extensionNames.contains(left.name)) {
|
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)
|
val ids = resolveImplicitThisMemberIds(left.name, left.pos(), receiverTypeName)
|
||||||
ImplicitThisMethodCallRef(
|
ImplicitThisMethodCallRef(
|
||||||
left.name,
|
left.name,
|
||||||
@ -7233,7 +7346,7 @@ class Compiler(
|
|||||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||||
}
|
}
|
||||||
if ((classContext || implicitThis) && extensionNames.contains(left.name)) {
|
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)
|
val ids = resolveImplicitThisMemberIds(left.name, left.pos(), receiverTypeName)
|
||||||
ImplicitThisMethodCallRef(
|
ImplicitThisMethodCallRef(
|
||||||
left.name,
|
left.name,
|
||||||
@ -7284,6 +7397,16 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun expectedCallableArgumentType(target: ObjRef, argIndex: Int): TypeDecl.Function? {
|
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
|
val decl = (resolveReceiverTypeDecl(target) ?: seedTypeDeclFromRef(target)) as? TypeDecl.Function
|
||||||
?: return null
|
?: return null
|
||||||
val params = when (target) {
|
val params = when (target) {
|
||||||
@ -7609,7 +7732,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<init>",
|
"<init>",
|
||||||
implicitThisMembers = true,
|
implicitThisMembers = true,
|
||||||
implicitThisTypeName = implicitThisType
|
implicitReceiverTypeNames = listOfNotNull(implicitThisType)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
parseBlock()
|
parseBlock()
|
||||||
@ -9728,7 +9851,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
name,
|
name,
|
||||||
implicitThisMembers = implicitThisMembers,
|
implicitThisMembers = implicitThisMembers,
|
||||||
implicitThisTypeName = implicitThisTypeName,
|
implicitReceiverTypeNames = listOfNotNull(implicitThisTypeName),
|
||||||
typeParams = typeParams,
|
typeParams = typeParams,
|
||||||
typeParamDecls = typeParamDecls,
|
typeParamDecls = typeParamDecls,
|
||||||
noImplicitThis = noImplicitThis
|
noImplicitThis = noImplicitThis
|
||||||
@ -10843,9 +10966,17 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val initialExpression = if (setNull || isProperty) null
|
val initialExpression = if (setNull || isProperty) {
|
||||||
else parseStatement(true)
|
null
|
||||||
?: throw ScriptError(effectiveEqToken!!.pos, "Expected initializer expression")
|
} 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) {
|
if (varTypeDecl == TypeDecl.TypeAny && initialExpression != null) {
|
||||||
val inferred = inferTypeDeclFromInitializer(initialExpression)
|
val inferred = inferTypeDeclFromInitializer(initialExpression)
|
||||||
@ -11071,7 +11202,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<getter>",
|
"<getter>",
|
||||||
implicitThisMembers = accessorImplicitThisMembers,
|
implicitThisMembers = accessorImplicitThisMembers,
|
||||||
implicitThisTypeName = accessorImplicitThisTypeName
|
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
wrapFunctionBytecode(parseBlock(), "<getter>")
|
wrapFunctionBytecode(parseBlock(), "<getter>")
|
||||||
@ -11083,7 +11214,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<getter>",
|
"<getter>",
|
||||||
implicitThisMembers = accessorImplicitThisMembers,
|
implicitThisMembers = accessorImplicitThisMembers,
|
||||||
implicitThisTypeName = accessorImplicitThisTypeName
|
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val expr = parseExpression()
|
val expr = parseExpression()
|
||||||
@ -11110,7 +11241,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<setter>",
|
"<setter>",
|
||||||
implicitThisMembers = accessorImplicitThisMembers,
|
implicitThisMembers = accessorImplicitThisMembers,
|
||||||
implicitThisTypeName = accessorImplicitThisTypeName
|
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
wrapFunctionBytecode(parseBlockWithPredeclared(listOf(setArgName to true)), "<setter>")
|
wrapFunctionBytecode(parseBlockWithPredeclared(listOf(setArgName to true)), "<setter>")
|
||||||
@ -11123,7 +11254,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<setter>",
|
"<setter>",
|
||||||
implicitThisMembers = accessorImplicitThisMembers,
|
implicitThisMembers = accessorImplicitThisMembers,
|
||||||
implicitThisTypeName = accessorImplicitThisTypeName
|
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
parseExpressionBlockWithPredeclared(listOf(setArgName to true))
|
parseExpressionBlockWithPredeclared(listOf(setArgName to true))
|
||||||
@ -11152,7 +11283,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<setter>",
|
"<setter>",
|
||||||
implicitThisMembers = accessorImplicitThisMembers,
|
implicitThisMembers = accessorImplicitThisMembers,
|
||||||
implicitThisTypeName = accessorImplicitThisTypeName
|
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
parseBlockWithPredeclared(listOf(setArg.value to true))
|
parseBlockWithPredeclared(listOf(setArg.value to true))
|
||||||
@ -11166,7 +11297,7 @@ class Compiler(
|
|||||||
CodeContext.Function(
|
CodeContext.Function(
|
||||||
"<setter>",
|
"<setter>",
|
||||||
implicitThisMembers = accessorImplicitThisMembers,
|
implicitThisMembers = accessorImplicitThisMembers,
|
||||||
implicitThisTypeName = accessorImplicitThisTypeName
|
implicitReceiverTypeNames = listOfNotNull(accessorImplicitThisTypeName)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
parseExpressionBlockWithPredeclared(listOf(setArg.value to true))
|
parseExpressionBlockWithPredeclared(listOf(setArg.value to true))
|
||||||
|
|||||||
@ -26,6 +26,7 @@ sealed class TypeDecl(val isNullable:Boolean = false) {
|
|||||||
// ??
|
// ??
|
||||||
data class Function(
|
data class Function(
|
||||||
val receiver: TypeDecl?,
|
val receiver: TypeDecl?,
|
||||||
|
val contextReceivers: List<TypeDecl> = emptyList(),
|
||||||
val params: List<TypeDecl>,
|
val params: List<TypeDecl>,
|
||||||
val returnType: TypeDecl,
|
val returnType: TypeDecl,
|
||||||
val nullable: Boolean = false
|
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 TypeGenericDoc(val base: TypeNameDoc, val args: List<TypeDoc>, override val nullable: Boolean = false) : TypeDoc
|
||||||
data class TypeFunctionDoc(
|
data class TypeFunctionDoc(
|
||||||
val receiver: TypeDoc? = null,
|
val receiver: TypeDoc? = null,
|
||||||
|
val contextReceivers: List<TypeDoc> = emptyList(),
|
||||||
val params: List<TypeDoc>,
|
val params: List<TypeDoc>,
|
||||||
val returns: TypeDoc,
|
val returns: TypeDoc,
|
||||||
override val nullable: Boolean = false
|
override val nullable: Boolean = false
|
||||||
@ -45,8 +46,13 @@ data class TypeVarDoc(val name: String, override val nullable: Boolean = false)
|
|||||||
// Convenience builders
|
// Convenience builders
|
||||||
fun type(name: String, nullable: Boolean = false) = TypeNameDoc(name.split('.'), nullable)
|
fun type(name: String, nullable: Boolean = false) = TypeNameDoc(name.split('.'), nullable)
|
||||||
fun typeVar(name: String, nullable: Boolean = false) = TypeVarDoc(name, nullable)
|
fun typeVar(name: String, nullable: Boolean = false) = TypeVarDoc(name, nullable)
|
||||||
fun funType(params: List<TypeDoc>, returns: TypeDoc, receiver: TypeDoc? = null, nullable: Boolean = false) =
|
fun funType(
|
||||||
TypeFunctionDoc(receiver, params, returns, nullable)
|
params: List<TypeDoc>,
|
||||||
|
returns: TypeDoc,
|
||||||
|
receiver: TypeDoc? = null,
|
||||||
|
contextReceivers: List<TypeDoc> = emptyList(),
|
||||||
|
nullable: Boolean = false
|
||||||
|
) = TypeFunctionDoc(receiver, contextReceivers, params, returns, nullable)
|
||||||
|
|
||||||
// ---------------- Registry ----------------
|
// ---------------- Registry ----------------
|
||||||
|
|
||||||
@ -281,6 +287,7 @@ internal fun TypeDoc.toMiniTypeRef(): MiniTypeRef = when (this) {
|
|||||||
is TypeFunctionDoc -> MiniFunctionType(
|
is TypeFunctionDoc -> MiniFunctionType(
|
||||||
range = builtinRange(),
|
range = builtinRange(),
|
||||||
receiver = this.receiver?.toMiniTypeRef(),
|
receiver = this.receiver?.toMiniTypeRef(),
|
||||||
|
contextReceivers = this.contextReceivers.map { it.toMiniTypeRef() },
|
||||||
params = this.params.map { it.toMiniTypeRef() },
|
params = this.params.map { it.toMiniTypeRef() },
|
||||||
returnType = this.returns.toMiniTypeRef(),
|
returnType = this.returns.toMiniTypeRef(),
|
||||||
nullable = this.nullable
|
nullable = this.nullable
|
||||||
|
|||||||
@ -139,6 +139,7 @@ data class MiniGenericType(
|
|||||||
data class MiniFunctionType(
|
data class MiniFunctionType(
|
||||||
override val range: MiniRange,
|
override val range: MiniRange,
|
||||||
val receiver: MiniTypeRef?,
|
val receiver: MiniTypeRef?,
|
||||||
|
val contextReceivers: List<MiniTypeRef>,
|
||||||
val params: List<MiniTypeRef>,
|
val params: List<MiniTypeRef>,
|
||||||
val returnType: MiniTypeRef,
|
val returnType: MiniTypeRef,
|
||||||
val nullable: Boolean
|
val nullable: Boolean
|
||||||
|
|||||||
@ -206,7 +206,17 @@ private fun typeDeclKey(type: TypeDecl): String = when (type) {
|
|||||||
TypeDecl.TypeNullableAny -> "Any?"
|
TypeDecl.TypeNullableAny -> "Any?"
|
||||||
is TypeDecl.Simple -> "S:${type.name}"
|
is TypeDecl.Simple -> "S:${type.name}"
|
||||||
is TypeDecl.Generic -> "G:${type.name}<${type.args.joinToString(",") { typeDeclKey(it) }}>"
|
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.Ellipsis -> "E:${typeDeclKey(type.elementType)}"
|
||||||
is TypeDecl.TypeVar -> "V:${type.name}"
|
is TypeDecl.TypeVar -> "V:${type.name}"
|
||||||
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
|
is TypeDecl.Union -> "U:${type.options.joinToString("|") { typeDeclKey(it) }}"
|
||||||
|
|||||||
@ -473,6 +473,29 @@ class MiniAstTest {
|
|||||||
assertEquals("b", fn.params[1].name)
|
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
|
@Test
|
||||||
fun miniAst_captures_dokka_tags() = runTest {
|
fun miniAst_captures_dokka_tags() = runTest {
|
||||||
val code = """
|
val code = """
|
||||||
|
|||||||
@ -154,4 +154,132 @@ class OptTest {
|
|||||||
assert( cnt == 4 )
|
assert( cnt == 4 )
|
||||||
""".trimIndent())
|
""".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