improved type inference in plugin
This commit is contained in:
parent
f6deabaa38
commit
1efa96a990
@ -117,9 +117,18 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
mini.declarations.filterIsInstance<MiniFunDecl>().forEach { fn ->
|
fun addParams(params: List<MiniParam>) {
|
||||||
if (fn.nameStart.source != source) return@forEach
|
params.forEach { p ->
|
||||||
fn.params.forEach { p -> putName(p.nameStart, p.name, LyngHighlighterColors.PARAMETER) }
|
if (p.nameStart.source == source)
|
||||||
|
putName(p.nameStart, p.name, LyngHighlighterColors.PARAMETER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mini.declarations.forEach { d ->
|
||||||
|
when (d) {
|
||||||
|
is MiniFunDecl -> addParams(d.params)
|
||||||
|
is MiniClassDecl -> d.members.filterIsInstance<MiniMemberFunDecl>().forEach { addParams(it.params) }
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type name segments (including generics base & args)
|
// Type name segments (including generics base & args)
|
||||||
@ -146,8 +155,8 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
null -> {}
|
null -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mini.declarations.forEach { d ->
|
fun addDeclTypeSegments(d: MiniDecl) {
|
||||||
if (d.nameStart.source != source) return@forEach
|
if (d.nameStart.source != source) return
|
||||||
when (d) {
|
when (d) {
|
||||||
is MiniFunDecl -> {
|
is MiniFunDecl -> {
|
||||||
addTypeSegments(d.returnType)
|
addTypeSegments(d.returnType)
|
||||||
@ -161,10 +170,23 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
is MiniClassDecl -> {
|
is MiniClassDecl -> {
|
||||||
d.ctorFields.forEach { addTypeSegments(it.type) }
|
d.ctorFields.forEach { addTypeSegments(it.type) }
|
||||||
d.classFields.forEach { addTypeSegments(it.type) }
|
d.classFields.forEach { addTypeSegments(it.type) }
|
||||||
|
for (m in d.members) {
|
||||||
|
when (m) {
|
||||||
|
is MiniMemberFunDecl -> {
|
||||||
|
addTypeSegments(m.returnType)
|
||||||
|
m.params.forEach { addTypeSegments(it.type) }
|
||||||
|
}
|
||||||
|
is MiniMemberValDecl -> {
|
||||||
|
addTypeSegments(m.type)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is MiniEnumDecl -> {}
|
is MiniEnumDecl -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mini.declarations.forEach { d -> addDeclTypeSegments(d) }
|
||||||
|
|
||||||
ProgressManager.checkCanceled()
|
ProgressManager.checkCanceled()
|
||||||
|
|
||||||
@ -212,6 +234,13 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
if (d.mutable) LyngHighlighterColors.VARIABLE else LyngHighlighterColors.VALUE
|
if (d.mutable) LyngHighlighterColors.VARIABLE else LyngHighlighterColors.VALUE
|
||||||
|
|
||||||
is MiniFunDecl -> d.params.forEach { p -> nameRole[p.name] = LyngHighlighterColors.PARAMETER }
|
is MiniFunDecl -> d.params.forEach { p -> nameRole[p.name] = LyngHighlighterColors.PARAMETER }
|
||||||
|
is MiniClassDecl -> {
|
||||||
|
d.members.forEach { m ->
|
||||||
|
if (m is MiniMemberFunDecl) {
|
||||||
|
m.params.forEach { p -> nameRole[p.name] = LyngHighlighterColors.PARAMETER }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,7 +116,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
if (memberDotPos != null && engineItems.isEmpty()) {
|
if (memberDotPos != null && engineItems.isEmpty()) {
|
||||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback: engine returned 0 in member context; trying local inference")
|
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback: engine returned 0 in member context; trying local inference")
|
||||||
// Build imported modules from text (lenient) + stdlib; avoid heavy MiniAst here
|
// Build imported modules from text (lenient) + stdlib; avoid heavy MiniAst here
|
||||||
val fromText = extractImportsFromText(text)
|
val fromText = DocLookupUtils.extractImportsFromText(text)
|
||||||
val imported = LinkedHashSet<String>().apply {
|
val imported = LinkedHashSet<String>().apply {
|
||||||
fromText.forEach { add(it) }
|
fromText.forEach { add(it) }
|
||||||
add("lyng.stdlib")
|
add("lyng.stdlib")
|
||||||
@ -176,7 +176,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
// In member context, ensure stdlib extension-like methods (e.g., String.re) are present
|
// In member context, ensure stdlib extension-like methods (e.g., String.re) are present
|
||||||
if (memberDotPos != null) {
|
if (memberDotPos != null) {
|
||||||
val existing = engineItems.map { it.name }.toMutableSet()
|
val existing = engineItems.map { it.name }.toMutableSet()
|
||||||
val fromText = extractImportsFromText(text)
|
val fromText = DocLookupUtils.extractImportsFromText(text)
|
||||||
val imported = LinkedHashSet<String>().apply {
|
val imported = LinkedHashSet<String>().apply {
|
||||||
fromText.forEach { add(it) }
|
fromText.forEach { add(it) }
|
||||||
add("lyng.stdlib")
|
add("lyng.stdlib")
|
||||||
@ -249,7 +249,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
// If in member context and engine items are suspiciously sparse, try to enrich via local inference + offerMembers
|
// If in member context and engine items are suspiciously sparse, try to enrich via local inference + offerMembers
|
||||||
if (memberDotPos != null && engineItems.size < 3) {
|
if (memberDotPos != null && engineItems.size < 3) {
|
||||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Engine produced only ${engineItems.size} items in member context — trying enrichment")
|
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Engine produced only ${engineItems.size} items in member context — trying enrichment")
|
||||||
val fromText = extractImportsFromText(text)
|
val fromText = DocLookupUtils.extractImportsFromText(text)
|
||||||
val imported = LinkedHashSet<String>().apply {
|
val imported = LinkedHashSet<String>().apply {
|
||||||
fromText.forEach { add(it) }
|
fromText.forEach { add(it) }
|
||||||
add("lyng.stdlib")
|
add("lyng.stdlib")
|
||||||
@ -410,13 +410,18 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
for (name in keys) {
|
for (name in keys) {
|
||||||
val list = map[name] ?: continue
|
val list = map[name] ?: continue
|
||||||
// Choose a representative for display:
|
// Choose a representative for display:
|
||||||
// 1) Prefer a method with a known return type
|
// 1) Prefer a method with return type AND parameters
|
||||||
// 2) Else any method
|
// 2) Prefer a method with parameters
|
||||||
// 3) Else the first variant
|
// 3) Prefer a method with return type
|
||||||
|
// 4) Else any method
|
||||||
|
// 5) Else the first variant
|
||||||
val rep =
|
val rep =
|
||||||
list.asSequence()
|
list.asSequence().filterIsInstance<MiniMemberFunDecl>()
|
||||||
.filterIsInstance<MiniMemberFunDecl>()
|
.firstOrNull { it.returnType != null && it.params.isNotEmpty() }
|
||||||
.firstOrNull { it.returnType != null }
|
?: list.asSequence().filterIsInstance<MiniMemberFunDecl>()
|
||||||
|
.firstOrNull { it.params.isNotEmpty() }
|
||||||
|
?: list.asSequence().filterIsInstance<MiniMemberFunDecl>()
|
||||||
|
.firstOrNull { it.returnType != null }
|
||||||
?: list.firstOrNull { it is MiniMemberFunDecl }
|
?: list.firstOrNull { it is MiniMemberFunDecl }
|
||||||
?: list.first()
|
?: list.first()
|
||||||
when (rep) {
|
when (rep) {
|
||||||
@ -603,32 +608,10 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lenient textual import extractor (duplicated from QuickDoc privately)
|
|
||||||
private fun extractImportsFromText(text: String): List<String> {
|
|
||||||
val result = LinkedHashSet<String>()
|
|
||||||
val re = Regex("^\\s*import\\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)*)", RegexOption.MULTILINE)
|
|
||||||
re.findAll(text).forEach { m ->
|
|
||||||
val raw = m.groupValues.getOrNull(1)?.trim().orEmpty()
|
|
||||||
if (raw.isNotEmpty()) {
|
|
||||||
val canon = if (raw.startsWith("lyng.")) raw else "lyng.$raw"
|
|
||||||
result.add(canon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun typeOf(t: MiniTypeRef?): String {
|
private fun typeOf(t: MiniTypeRef?): String {
|
||||||
return when (t) {
|
val s = DocLookupUtils.typeOf(t)
|
||||||
null -> ""
|
return if (s.isEmpty()) "" else ": $s"
|
||||||
is MiniTypeName -> t.segments.lastOrNull()?.name?.let { ": $it" } ?: ""
|
|
||||||
is MiniGenericType -> {
|
|
||||||
val base = typeOf(t.base).removePrefix(": ")
|
|
||||||
val args = t.args.joinToString(",") { typeOf(it).removePrefix(": ") }
|
|
||||||
": ${base}<${args}>"
|
|
||||||
}
|
|
||||||
is MiniFunctionType -> ": (fn)"
|
|
||||||
is MiniTypeVar -> ": ${t.name}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -561,16 +561,9 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun typeOf(t: MiniTypeRef?): String = when (t) {
|
private fun typeOf(t: MiniTypeRef?): String {
|
||||||
is MiniTypeName -> ": ${t.segments.joinToString(".") { it.name }}${if (t.nullable) "?" else ""}"
|
val s = DocLookupUtils.typeOf(t)
|
||||||
is MiniGenericType -> {
|
return if (s.isEmpty()) (if (t == null) ": Object?" else "") else ": $s"
|
||||||
val base = typeOf(t.base).removePrefix(": ")
|
|
||||||
val args = t.args.joinToString(", ") { typeOf(it).removePrefix(": ") }
|
|
||||||
": ${base}<${args}>${if (t.nullable) "?" else ""}"
|
|
||||||
}
|
|
||||||
is MiniFunctionType -> ": (..) -> ..${if (t.nullable) "?" else ""}"
|
|
||||||
is MiniTypeVar -> ": ${t.name}${if (t.nullable) "?" else ""}"
|
|
||||||
null -> ": Object?"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun signatureOf(fn: MiniFunDecl): String {
|
private fun signatureOf(fn: MiniFunDecl): String {
|
||||||
|
|||||||
@ -917,12 +917,13 @@ class Compiler(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type information (semantic + mini syntax)
|
||||||
|
val (typeInfo, miniType) = parseTypeDeclarationWithMini()
|
||||||
|
|
||||||
var defaultValue: Statement? = null
|
var defaultValue: Statement? = null
|
||||||
cc.ifNextIs(Token.Type.ASSIGN) {
|
cc.ifNextIs(Token.Type.ASSIGN) {
|
||||||
defaultValue = parseExpression()
|
defaultValue = parseExpression()
|
||||||
}
|
}
|
||||||
// type information (semantic + mini syntax)
|
|
||||||
val (typeInfo, miniType) = parseTypeDeclarationWithMini()
|
|
||||||
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true)
|
||||||
result += ArgsDeclaration.Item(
|
result += ArgsDeclaration.Item(
|
||||||
t.value,
|
t.value,
|
||||||
|
|||||||
@ -35,7 +35,8 @@ data class Symbol(
|
|||||||
val kind: SymbolKind,
|
val kind: SymbolKind,
|
||||||
val declStart: Int,
|
val declStart: Int,
|
||||||
val declEnd: Int,
|
val declEnd: Int,
|
||||||
val containerId: Int?
|
val containerId: Int?,
|
||||||
|
val type: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Reference(val symbolId: Int, val start: Int, val end: Int)
|
data class Reference(val symbolId: Int, val start: Int, val end: Int)
|
||||||
@ -97,59 +98,83 @@ object Binder {
|
|||||||
// First pass (classes only): register classes so we can attach methods/fields
|
// First pass (classes only): register classes so we can attach methods/fields
|
||||||
for (d in mini.declarations) if (d is MiniClassDecl) {
|
for (d in mini.declarations) if (d is MiniClassDecl) {
|
||||||
val (s, e) = nameOffsets(d.nameStart, d.name)
|
val (s, e) = nameOffsets(d.nameStart, d.name)
|
||||||
val sym = Symbol(nextId++, d.name, SymbolKind.Class, s, e, containerId = null)
|
val sym = Symbol(nextId++, d.name, SymbolKind.Class, s, e, containerId = null, type = d.name)
|
||||||
symbols += sym
|
symbols += sym
|
||||||
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
||||||
// Prefer explicit body range; otherwise use the whole class declaration range
|
// Prefer explicit body range; otherwise use the whole class declaration range
|
||||||
val bodyStart = d.bodyRange?.start?.let { source.offsetOf(it) } ?: source.offsetOf(d.range.start)
|
val bodyStart = d.bodyRange?.start?.let { source.offsetOf(it) } ?: source.offsetOf(d.range.start)
|
||||||
val bodyEnd = d.bodyRange?.end?.let { source.offsetOf(it) } ?: source.offsetOf(d.range.end)
|
val bodyEnd = d.bodyRange?.end?.let { source.offsetOf(it) } ?: source.offsetOf(d.range.end)
|
||||||
classes += ClassScope(sym.id, bodyStart, bodyEnd, mutableListOf())
|
val classScope = ClassScope(sym.id, bodyStart, bodyEnd, mutableListOf())
|
||||||
|
classes += classScope
|
||||||
// Constructor fields (val/var in primary ctor)
|
// Constructor fields (val/var in primary ctor)
|
||||||
for (cf in d.ctorFields) {
|
for (cf in d.ctorFields) {
|
||||||
val fs = source.offsetOf(cf.nameStart)
|
val fs = source.offsetOf(cf.nameStart)
|
||||||
val fe = fs + cf.name.length
|
val fe = fs + cf.name.length
|
||||||
val kind = if (cf.mutable) SymbolKind.Variable else SymbolKind.Value
|
val kind = if (cf.mutable) SymbolKind.Variable else SymbolKind.Value
|
||||||
val fieldSym = Symbol(nextId++, cf.name, kind, fs, fe, containerId = sym.id)
|
val fieldSym = Symbol(nextId++, cf.name, kind, fs, fe, containerId = sym.id, type = DocLookupUtils.typeOf(cf.type))
|
||||||
symbols += fieldSym
|
symbols += fieldSym
|
||||||
classes.last().fields += fieldSym.id
|
classScope.fields += fieldSym.id
|
||||||
}
|
}
|
||||||
// Class fields (val/var in class body, if any are reported here)
|
// Class fields (val/var in class body, if any are reported here)
|
||||||
for (cf in d.classFields) {
|
for (cf in d.classFields) {
|
||||||
val fs = source.offsetOf(cf.nameStart)
|
val fs = source.offsetOf(cf.nameStart)
|
||||||
val fe = fs + cf.name.length
|
val fe = fs + cf.name.length
|
||||||
val kind = if (cf.mutable) SymbolKind.Variable else SymbolKind.Value
|
val kind = if (cf.mutable) SymbolKind.Variable else SymbolKind.Value
|
||||||
val fieldSym = Symbol(nextId++, cf.name, kind, fs, fe, containerId = sym.id)
|
val fieldSym = Symbol(nextId++, cf.name, kind, fs, fe, containerId = sym.id, type = DocLookupUtils.typeOf(cf.type))
|
||||||
symbols += fieldSym
|
symbols += fieldSym
|
||||||
classes.last().fields += fieldSym.id
|
classScope.fields += fieldSym.id
|
||||||
}
|
}
|
||||||
|
// Members (including fields and methods)
|
||||||
|
for (m in d.members) {
|
||||||
|
if (m is MiniMemberValDecl) {
|
||||||
|
val fs = source.offsetOf(m.nameStart)
|
||||||
|
val fe = fs + m.name.length
|
||||||
|
val kind = if (m.mutable) SymbolKind.Variable else SymbolKind.Value
|
||||||
|
val fieldSym = Symbol(nextId++, m.name, kind, fs, fe, containerId = sym.id, type = DocLookupUtils.typeOf(m.type))
|
||||||
|
symbols += fieldSym
|
||||||
|
classScope.fields += fieldSym.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerFun(name: String, nameStart: net.sergeych.lyng.Pos, params: List<MiniParam>, returnType: MiniTypeRef?, bodyRange: MiniRange?, isTopLevel: Boolean) {
|
||||||
|
val (s, e) = nameOffsets(nameStart, name)
|
||||||
|
val ownerClass = classContaining(s)
|
||||||
|
val sym = Symbol(nextId++, name, SymbolKind.Function, s, e, containerId = ownerClass?.symId, type = DocLookupUtils.typeOf(returnType))
|
||||||
|
symbols += sym
|
||||||
|
if (isTopLevel) {
|
||||||
|
topLevelByName.getOrPut(name) { mutableListOf() }.add(sym.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine body range if present; otherwise, derive a conservative end at decl range end
|
||||||
|
val bodyStart = bodyRange?.start?.let { source.offsetOf(it) } ?: e
|
||||||
|
val bodyEnd = bodyRange?.end?.let { source.offsetOf(it) } ?: e
|
||||||
|
val fnScope = FnScope(sym.id, bodyStart, bodyEnd, mutableListOf(), ownerClass?.symId)
|
||||||
|
|
||||||
|
// Params
|
||||||
|
for (p in params) {
|
||||||
|
val ps = source.offsetOf(p.nameStart)
|
||||||
|
val pe = ps + p.name.length
|
||||||
|
val pk = SymbolKind.Parameter
|
||||||
|
val paramSym = Symbol(nextId++, p.name, pk, ps, pe, containerId = sym.id, type = DocLookupUtils.typeOf(p.type))
|
||||||
|
fnScope.locals += paramSym.id
|
||||||
|
symbols += paramSym
|
||||||
|
}
|
||||||
|
functions += fnScope
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: functions and top-level/class vals/vars
|
// Second pass: functions and top-level/class vals/vars
|
||||||
for (d in mini.declarations) {
|
for (d in mini.declarations) {
|
||||||
when (d) {
|
when (d) {
|
||||||
is MiniClassDecl -> { /* already processed in first pass */ }
|
is MiniClassDecl -> {
|
||||||
is MiniFunDecl -> {
|
for (m in d.members) {
|
||||||
val (s, e) = nameOffsets(d.nameStart, d.name)
|
if (m is MiniMemberFunDecl) {
|
||||||
val ownerClass = classContaining(s)
|
registerFun(m.name, m.nameStart, m.params, m.returnType, m.body?.range, false)
|
||||||
val sym = Symbol(nextId++, d.name, SymbolKind.Function, s, e, containerId = ownerClass?.symId)
|
}
|
||||||
symbols += sym
|
|
||||||
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
|
||||||
|
|
||||||
// Determine body range if present; otherwise, derive a conservative end at decl range end
|
|
||||||
val bodyStart = d.body?.range?.start?.let { source.offsetOf(it) } ?: e
|
|
||||||
val bodyEnd = d.body?.range?.end?.let { source.offsetOf(it) } ?: e
|
|
||||||
val fnScope = FnScope(sym.id, bodyStart, bodyEnd, mutableListOf(), ownerClass?.symId)
|
|
||||||
|
|
||||||
// Params
|
|
||||||
for (p in d.params) {
|
|
||||||
val ps = source.offsetOf(p.nameStart)
|
|
||||||
val pe = ps + p.name.length
|
|
||||||
val pk = SymbolKind.Parameter
|
|
||||||
val paramSym = Symbol(nextId++, p.name, pk, ps, pe, containerId = sym.id)
|
|
||||||
fnScope.locals += paramSym.id
|
|
||||||
symbols += paramSym
|
|
||||||
}
|
}
|
||||||
functions += fnScope
|
}
|
||||||
|
is MiniFunDecl -> {
|
||||||
|
registerFun(d.name, d.nameStart, d.params, d.returnType, d.body?.range, true)
|
||||||
}
|
}
|
||||||
is MiniValDecl -> {
|
is MiniValDecl -> {
|
||||||
val (s, e) = nameOffsets(d.nameStart, d.name)
|
val (s, e) = nameOffsets(d.nameStart, d.name)
|
||||||
@ -157,18 +182,18 @@ object Binder {
|
|||||||
val ownerClass = classContaining(s)
|
val ownerClass = classContaining(s)
|
||||||
if (ownerClass != null) {
|
if (ownerClass != null) {
|
||||||
// class field
|
// class field
|
||||||
val fieldSym = Symbol(nextId++, d.name, kind, s, e, containerId = ownerClass.symId)
|
val fieldSym = Symbol(nextId++, d.name, kind, s, e, containerId = ownerClass.symId, type = DocLookupUtils.typeOf(d.type))
|
||||||
symbols += fieldSym
|
symbols += fieldSym
|
||||||
ownerClass.fields += fieldSym.id
|
ownerClass.fields += fieldSym.id
|
||||||
} else {
|
} else {
|
||||||
val sym = Symbol(nextId++, d.name, kind, s, e, containerId = null)
|
val sym = Symbol(nextId++, d.name, kind, s, e, containerId = null, type = DocLookupUtils.typeOf(d.type))
|
||||||
symbols += sym
|
symbols += sym
|
||||||
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MiniEnumDecl -> {
|
is MiniEnumDecl -> {
|
||||||
val (s, e) = nameOffsets(d.nameStart, d.name)
|
val (s, e) = nameOffsets(d.nameStart, d.name)
|
||||||
val sym = Symbol(nextId++, d.name, SymbolKind.Enum, s, e, containerId = null)
|
val sym = Symbol(nextId++, d.name, SymbolKind.Enum, s, e, containerId = null, type = d.name)
|
||||||
symbols += sym
|
symbols += sym
|
||||||
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
||||||
}
|
}
|
||||||
@ -187,7 +212,7 @@ object Binder {
|
|||||||
"iterator", "hasNext", "next"
|
"iterator", "hasNext", "next"
|
||||||
)
|
)
|
||||||
for (name in stdFns) {
|
for (name in stdFns) {
|
||||||
val sym = Symbol(nextId++, name, SymbolKind.Function, 0, name.length, containerId = null)
|
val sym = Symbol(nextId++, name, SymbolKind.Function, 0, name.length, containerId = null, type = null)
|
||||||
symbols += sym
|
symbols += sym
|
||||||
topLevelByName.getOrPut(name) { mutableListOf() }.add(sym.id)
|
topLevelByName.getOrPut(name) { mutableListOf() }.add(sym.id)
|
||||||
}
|
}
|
||||||
@ -204,7 +229,7 @@ object Binder {
|
|||||||
if (containerFn != null) {
|
if (containerFn != null) {
|
||||||
val fnSymId = containerFn.id
|
val fnSymId = containerFn.id
|
||||||
val kind = if (d.mutable) SymbolKind.Variable else SymbolKind.Value
|
val kind = if (d.mutable) SymbolKind.Variable else SymbolKind.Value
|
||||||
val localSym = Symbol(nextId++, d.name, kind, s, e, containerId = fnSymId)
|
val localSym = Symbol(nextId++, d.name, kind, s, e, containerId = fnSymId, type = DocLookupUtils.typeOf(d.type))
|
||||||
symbols += localSym
|
symbols += localSym
|
||||||
containerFn.locals += localSym.id
|
containerFn.locals += localSym.id
|
||||||
}
|
}
|
||||||
@ -245,11 +270,11 @@ object Binder {
|
|||||||
.maxByOrNull { it.rangeEnd - it.rangeStart }
|
.maxByOrNull { it.rangeEnd - it.rangeStart }
|
||||||
val kind = if (kw.equals("var", true)) SymbolKind.Variable else SymbolKind.Value
|
val kind = if (kw.equals("var", true)) SymbolKind.Variable else SymbolKind.Value
|
||||||
if (inFn != null) {
|
if (inFn != null) {
|
||||||
val localSym = Symbol(nextId++, text.substring(nameStart, nameEnd), kind, nameStart, nameEnd, containerId = inFn.id)
|
val localSym = Symbol(nextId++, text.substring(nameStart, nameEnd), kind, nameStart, nameEnd, containerId = inFn.id, type = null)
|
||||||
symbols += localSym
|
symbols += localSym
|
||||||
inFn.locals += localSym.id
|
inFn.locals += localSym.id
|
||||||
} else {
|
} else {
|
||||||
val localSym = Symbol(nextId++, text.substring(nameStart, nameEnd), kind, nameStart, nameEnd, containerId = null)
|
val localSym = Symbol(nextId++, text.substring(nameStart, nameEnd), kind, nameStart, nameEnd, containerId = null, type = null)
|
||||||
symbols += localSym
|
symbols += localSym
|
||||||
topLevelByName.getOrPut(localSym.name) { mutableListOf() }.add(localSym.id)
|
topLevelByName.getOrPut(localSym.name) { mutableListOf() }.add(localSym.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -572,14 +572,20 @@ private fun buildStdlibDocs(): List<MiniDecl> {
|
|||||||
mod.classDoc(name = "Iterable", doc = StdlibInlineDocIndex.classDoc("Iterable") ?: "Helper operations for iterable collections.", bases = listOf(type("Obj"))) {
|
mod.classDoc(name = "Iterable", doc = StdlibInlineDocIndex.classDoc("Iterable") ?: "Helper operations for iterable collections.", bases = listOf(type("Obj"))) {
|
||||||
fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Iterable", name) ?: fallback
|
fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Iterable", name) ?: fallback
|
||||||
method(name = "filter", doc = md("filter", "Filter elements by predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
|
method(name = "filter", doc = md("filter", "Filter elements by predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
|
||||||
|
method(name = "filterFlow", doc = md("filterFlow", "Filter elements by predicate and return a Flow."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Flow"))
|
||||||
|
method(name = "filterNotNull", doc = md("filterNotNull", "Filter non-null elements."), returns = type("lyng.List"))
|
||||||
method(name = "drop", doc = md("drop", "Skip the first N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
|
method(name = "drop", doc = md("drop", "Skip the first N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
|
||||||
method(name = "first", doc = md("first", "Return the first element or throw if empty."))
|
field(name = "first", doc = md("first", "Return the first element or throw if empty."))
|
||||||
method(name = "last", doc = md("last", "Return the last element or throw if empty."))
|
field(name = "last", doc = md("last", "Return the last element or throw if empty."))
|
||||||
|
method(name = "findFirst", doc = md("findFirst", "Return the first matching element or throw."), params = listOf(ParamDoc("predicate")))
|
||||||
|
method(name = "findFirstOrNull", doc = md("findFirstOrNull", "Return the first matching element or null."), params = listOf(ParamDoc("predicate")))
|
||||||
method(name = "dropLast", doc = md("dropLast", "Drop the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
|
method(name = "dropLast", doc = md("dropLast", "Drop the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
|
||||||
method(name = "takeLast", doc = md("takeLast", "Take the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"))
|
method(name = "takeLast", doc = md("takeLast", "Take the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"))
|
||||||
method(name = "joinToString", doc = md("joinToString", "Join elements into a string with an optional separator and transformer."), params = listOf(ParamDoc("prefix", type("lyng.String")), ParamDoc("transformer")), returns = type("lyng.String"))
|
method(name = "joinToString", doc = md("joinToString", "Join elements into a string with an optional separator and transformer."), params = listOf(ParamDoc("separator", type("lyng.String")), ParamDoc("transformer")), returns = type("lyng.String"))
|
||||||
method(name = "any", doc = md("any", "Return true if any element matches the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
|
method(name = "any", doc = md("any", "Return true if any element matches the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
|
||||||
method(name = "all", doc = md("all", "Return true if all elements match the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
|
method(name = "all", doc = md("all", "Return true if all elements match the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
|
||||||
|
method(name = "forEach", doc = md("forEach", "Execute `action` for each element."), params = listOf(ParamDoc("action")))
|
||||||
|
method(name = "count", doc = md("count", "Count elements matching the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Int"))
|
||||||
method(name = "sum", doc = md("sum", "Sum all elements; returns null for empty collections."), returns = type("lyng.Number", nullable = true))
|
method(name = "sum", doc = md("sum", "Sum all elements; returns null for empty collections."), returns = type("lyng.Number", nullable = true))
|
||||||
method(name = "sumOf", doc = md("sumOf", "Sum mapped values of elements; returns null for empty collections."), params = listOf(ParamDoc("f")))
|
method(name = "sumOf", doc = md("sumOf", "Sum mapped values of elements; returns null for empty collections."), params = listOf(ParamDoc("f")))
|
||||||
method(name = "minOf", doc = md("minOf", "Minimum of mapped values."), params = listOf(ParamDoc("lambda")))
|
method(name = "minOf", doc = md("minOf", "Minimum of mapped values."), params = listOf(ParamDoc("lambda")))
|
||||||
@ -628,7 +634,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
|
|||||||
|
|
||||||
mod.classDoc(name = "String", doc = StdlibInlineDocIndex.classDoc("String") ?: "String helpers.", bases = listOf(type("Obj"))) {
|
mod.classDoc(name = "String", doc = StdlibInlineDocIndex.classDoc("String") ?: "String helpers.", bases = listOf(type("Obj"))) {
|
||||||
// Only include inline-source method here; Kotlin-embedded methods are now documented via DocHelpers near definitions.
|
// Only include inline-source method here; Kotlin-embedded methods are now documented via DocHelpers near definitions.
|
||||||
method(name = "re", doc = StdlibInlineDocIndex.methodDoc("String", "re") ?: "Compile this string into a regular expression.", returns = type("lyng.Regex"))
|
method(name = "re", doc = StdlibInlineDocIndex.methodDoc("String", "re") ?: "Compile this string into a regular expression.", params = listOf(ParamDoc("flags", type("lyng.String"))), returns = type("lyng.Regex"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackTraceEntry structure
|
// StackTraceEntry structure
|
||||||
|
|||||||
@ -74,6 +74,7 @@ object CompletionEngineLight {
|
|||||||
val word = DocLookupUtils.wordRangeAt(text, caret)
|
val word = DocLookupUtils.wordRangeAt(text, caret)
|
||||||
val memberDot = DocLookupUtils.findDotLeft(text, word?.first ?: caret)
|
val memberDot = DocLookupUtils.findDotLeft(text, word?.first ?: caret)
|
||||||
if (memberDot != null) {
|
if (memberDot != null) {
|
||||||
|
val inferredCls = (DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))
|
||||||
// 0) Try chained member call return type inference
|
// 0) Try chained member call return type inference
|
||||||
DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini)
|
||||||
@ -254,11 +255,19 @@ object CompletionEngineLight {
|
|||||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>, groupPriority: Double) {
|
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>, groupPriority: Double) {
|
||||||
for (name in map.keys.sortedBy { it.lowercase() }) {
|
for (name in map.keys.sortedBy { it.lowercase() }) {
|
||||||
val variants = map[name] ?: continue
|
val variants = map[name] ?: continue
|
||||||
// Prefer a method with a known return type; else any method; else first variant
|
// Choose a representative for display:
|
||||||
|
// 1) Prefer a method with return type AND parameters
|
||||||
|
// 2) Prefer a method with parameters
|
||||||
|
// 3) Prefer a method with return type
|
||||||
|
// 4) Else any method
|
||||||
|
// 5) Else the first variant
|
||||||
val rep =
|
val rep =
|
||||||
variants.asSequence()
|
variants.asSequence().filterIsInstance<MiniMemberFunDecl>()
|
||||||
.filterIsInstance<MiniMemberFunDecl>()
|
.firstOrNull { it.returnType != null && it.params.isNotEmpty() }
|
||||||
.firstOrNull { it.returnType != null }
|
?: variants.asSequence().filterIsInstance<MiniMemberFunDecl>()
|
||||||
|
.firstOrNull { it.params.isNotEmpty() }
|
||||||
|
?: variants.asSequence().filterIsInstance<MiniMemberFunDecl>()
|
||||||
|
.firstOrNull { it.returnType != null }
|
||||||
?: variants.firstOrNull { it is MiniMemberFunDecl }
|
?: variants.firstOrNull { it is MiniMemberFunDecl }
|
||||||
?: variants.first()
|
?: variants.first()
|
||||||
when (rep) {
|
when (rep) {
|
||||||
@ -336,16 +345,9 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun typeOf(t: MiniTypeRef?): String = when (t) {
|
private fun typeOf(t: MiniTypeRef?): String {
|
||||||
null -> ""
|
val s = DocLookupUtils.typeOf(t)
|
||||||
is MiniTypeName -> t.segments.lastOrNull()?.name?.let { ": $it" } ?: ""
|
return if (s.isEmpty()) "" else ": $s"
|
||||||
is MiniGenericType -> {
|
|
||||||
val base = typeOf(t.base).removePrefix(": ")
|
|
||||||
val args = t.args.joinToString(",") { typeOf(it).removePrefix(": ") }
|
|
||||||
": ${base}<${args}>"
|
|
||||||
}
|
|
||||||
is MiniFunctionType -> ": (fn)"
|
|
||||||
is MiniTypeVar -> ": ${t.name}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: we intentionally skip "params in scope" in the isolated engine to avoid PSI/offset mapping.
|
// Note: we intentionally skip "params in scope" in the isolated engine to avoid PSI/offset mapping.
|
||||||
|
|||||||
@ -78,6 +78,11 @@ object DocLookupUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (m in members) {
|
for (m in members) {
|
||||||
|
if (m is MiniMemberFunDecl) {
|
||||||
|
for (p in m.params) {
|
||||||
|
if (matches(p.nameStart, p.name.length)) return p.name to "Parameter"
|
||||||
|
}
|
||||||
|
}
|
||||||
if (matches(m.nameStart, m.name.length)) {
|
if (matches(m.nameStart, m.name.length)) {
|
||||||
val kind = when (m) {
|
val kind = when (m) {
|
||||||
is MiniMemberFunDecl -> "Function"
|
is MiniMemberFunDecl -> "Function"
|
||||||
@ -113,12 +118,18 @@ object DocLookupUtils {
|
|||||||
|
|
||||||
if (d is MiniClassDecl) {
|
if (d is MiniClassDecl) {
|
||||||
for (m in d.members) {
|
for (m in d.members) {
|
||||||
|
if (m is MiniMemberFunDecl) {
|
||||||
|
for (p in m.params) {
|
||||||
|
if (p.name == name && matches(p.nameStart, p.name.length)) return p.type
|
||||||
|
}
|
||||||
|
}
|
||||||
if (m.name == name && matches(m.nameStart, m.name.length)) {
|
if (m.name == name && matches(m.nameStart, m.name.length)) {
|
||||||
return when (m) {
|
return when (m) {
|
||||||
is MiniMemberFunDecl -> m.returnType
|
is MiniMemberFunDecl -> m.returnType
|
||||||
is MiniMemberValDecl -> m.type ?: if (text != null && imported != null) {
|
is MiniMemberValDecl -> m.type ?: if (text != null && imported != null) {
|
||||||
inferTypeRefFromInitRange(m.initRange, m.nameStart, text, imported, mini)
|
inferTypeRefFromInitRange(m.initRange, m.nameStart, text, imported, mini)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,11 +279,20 @@ object DocLookupUtils {
|
|||||||
dfs(baseName, visited)?.let { return it }
|
dfs(baseName, visited)?.let { return it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for local extensions in this class or bases
|
// 1) local extensions in this class or bases
|
||||||
localMini?.declarations?.firstOrNull { d ->
|
localMini?.declarations?.firstOrNull { d ->
|
||||||
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
||||||
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
||||||
}?.let { return name to it }
|
}?.let { return name to it as MiniNamedDecl }
|
||||||
|
|
||||||
|
// 2) built-in extensions from BuiltinDocRegistry
|
||||||
|
for (mod in importedModules) {
|
||||||
|
val decls = BuiltinDocRegistry.docsForModule(mod)
|
||||||
|
decls.firstOrNull { d ->
|
||||||
|
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
||||||
|
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
||||||
|
}?.let { return name to it as MiniNamedDecl }
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -970,6 +990,17 @@ object DocLookupUtils {
|
|||||||
is MiniTypeVar -> null
|
is MiniTypeVar -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun typeOf(t: MiniTypeRef?): String = when (t) {
|
||||||
|
is MiniTypeName -> t.segments.joinToString(".") { it.name } + (if (t.nullable) "?" else "")
|
||||||
|
is MiniGenericType -> typeOf(t.base) + "<" + t.args.joinToString(", ") { typeOf(it) } + ">" + (if (t.nullable) "?" else "")
|
||||||
|
is MiniFunctionType -> {
|
||||||
|
val r = t.receiver?.let { typeOf(it) + "." } ?: ""
|
||||||
|
r + "(" + t.params.joinToString(", ") { typeOf(it) } + ") -> " + typeOf(t.returnType) + (if (t.nullable) "?" else "")
|
||||||
|
}
|
||||||
|
is MiniTypeVar -> t.name + (if (t.nullable) "?" else "")
|
||||||
|
null -> ""
|
||||||
|
}
|
||||||
|
|
||||||
fun findDotLeft(text: String, offset: Int): Int? {
|
fun findDotLeft(text: String, offset: Int): Int? {
|
||||||
var i = (offset - 1).coerceAtLeast(0)
|
var i = (offset - 1).coerceAtLeast(0)
|
||||||
while (i >= 0 && text[i].isWhitespace()) i--
|
while (i >= 0 && text[i].isWhitespace()) i--
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,10 +18,7 @@
|
|||||||
package net.sergeych.lynon
|
package net.sergeych.lynon
|
||||||
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.obj.ObjBitBuffer
|
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
|
||||||
import net.sergeych.lyng.obj.ObjString
|
|
||||||
|
|
||||||
// Most often used types:
|
// Most often used types:
|
||||||
|
|
||||||
@ -53,14 +50,35 @@ object ObjLynonClass : ObjClass("Lynon") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode any object into Lynon format. Note that it has a special
|
||||||
|
* handling for void values, returning an empty byte array.
|
||||||
|
*
|
||||||
|
* This is the default behavior for encoding void values in Lynon format,
|
||||||
|
* ensuring consistency with decoding behavior. It matches the [lynonDecodeAny]
|
||||||
|
* behavior for handling void values.
|
||||||
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
suspend fun lynonEncodeAny(scope: Scope, value: Obj): UByteArray =
|
suspend fun lynonEncodeAny(scope: Scope, value: Obj): UByteArray =
|
||||||
(ObjLynonClass.encodeAny(scope, value))
|
if (value == ObjVoid)
|
||||||
.bitArray.asUByteArray()
|
ubyteArrayOf()
|
||||||
|
else
|
||||||
|
(ObjLynonClass.encodeAny(scope, value))
|
||||||
|
.bitArray.asUByteArray()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode any object from Lynon format. If the input is empty, returns ObjVoid.
|
||||||
|
* This behavior is designed to handle cases where the input data might be incomplete
|
||||||
|
* or intentionally left empty, indicating a void or null value and matches
|
||||||
|
* the [lynonEncodeAny] behavior [ObjVoid].
|
||||||
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
suspend fun lynonDecodeAny(scope: Scope, encoded: UByteArray): Obj =
|
suspend fun lynonDecodeAny(scope: Scope, encoded: UByteArray): Obj =
|
||||||
ObjLynonClass.decodeAny(
|
if (encoded.isEmpty())
|
||||||
|
ObjVoid
|
||||||
|
else
|
||||||
|
ObjLynonClass.decodeAny(
|
||||||
scope,
|
scope,
|
||||||
ObjBitBuffer(
|
ObjBitBuffer(
|
||||||
BitArray(encoded, 8)
|
BitArray(encoded, 8)
|
||||||
|
|||||||
@ -458,4 +458,19 @@ class MiniAstTest {
|
|||||||
val className = DocLookupUtils.simpleClassNameOf(type)
|
val className = DocLookupUtils.simpleClassNameOf(type)
|
||||||
assertEquals("List", className)
|
assertEquals("List", className)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun miniAst_captures_fun_with_type_and_default() = runTest {
|
||||||
|
val code = """
|
||||||
|
fun foo(a: Int, b: String = "ok"): Bool { true }
|
||||||
|
""".trimIndent()
|
||||||
|
val (_, sink) = compileWithMini(code)
|
||||||
|
val mini = sink.build()
|
||||||
|
assertNotNull(mini)
|
||||||
|
val fn = mini.declarations.filterIsInstance<MiniFunDecl>().firstOrNull { it.name == "foo" }
|
||||||
|
assertNotNull(fn)
|
||||||
|
assertEquals(2, fn.params.size)
|
||||||
|
assertEquals("a", fn.params[0].name)
|
||||||
|
assertEquals("b", fn.params[1].name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.miniast
|
||||||
|
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.Compiler
|
||||||
|
import net.sergeych.lyng.binding.Binder
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class ParamTypeInferenceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testParameterTypeInference() = runTest {
|
||||||
|
val code = """
|
||||||
|
class A {
|
||||||
|
fun foo(p: String) {
|
||||||
|
p.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(q: Int) {
|
||||||
|
q.
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val sink = MiniAstBuilder()
|
||||||
|
Compiler.compileWithMini(code.trimIndent(), sink)
|
||||||
|
val mini = sink.build()!!
|
||||||
|
val binding = Binder.bind(code, mini)
|
||||||
|
|
||||||
|
val dotPosQ = code.indexOf("q.") + 1
|
||||||
|
val receiverClassQ = DocLookupUtils.guessReceiverClassViaMini(mini, code, dotPosQ, listOf("lyng.stdlib"), binding)
|
||||||
|
assertEquals("Int", receiverClassQ, "Should infer type of parameter q in top-level function")
|
||||||
|
|
||||||
|
val dotPosP = code.indexOf("p.") + 1
|
||||||
|
val receiverClassP = DocLookupUtils.guessReceiverClassViaMini(mini, code, dotPosP, listOf("lyng.stdlib"), binding)
|
||||||
|
assertEquals("String", receiverClassP, "Should infer type of parameter p in member function")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -170,16 +170,9 @@ fun ReferencePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- helpers (mirror IDE provider minimal renderers) ---
|
// --- helpers (mirror IDE provider minimal renderers) ---
|
||||||
private fun typeOf(t: MiniTypeRef?): String = when (t) {
|
private fun typeOf(t: MiniTypeRef?): String {
|
||||||
is MiniTypeName -> ": " + t.segments.joinToString(".") { it.name } + if (t.nullable) "?" else ""
|
val s = DocLookupUtils.typeOf(t)
|
||||||
is MiniGenericType -> {
|
return if (s.isEmpty()) "" else ": $s"
|
||||||
val base = typeOf(t.base).removePrefix(": ")
|
|
||||||
val args = t.args.joinToString(", ") { typeOf(it).removePrefix(": ") }
|
|
||||||
": ${base}<${args}>" + if (t.nullable) "?" else ""
|
|
||||||
}
|
|
||||||
is MiniFunctionType -> ": (..) -> .." + if (t.nullable) "?" else ""
|
|
||||||
is MiniTypeVar -> ": ${t.name}" + if (t.nullable) "?" else ""
|
|
||||||
null -> ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun signatureOf(fn: MiniFunDecl): String {
|
private fun signatureOf(fn: MiniFunDecl): String {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user