Add support for symbol inclusion via directive and refactor extern classes handling

This commit is contained in:
Sergey Chernov 2026-03-15 09:13:33 +03:00
parent d2a47d34a3
commit a6492bb750
4 changed files with 90 additions and 30 deletions

View File

@ -36,6 +36,7 @@ object LyngAstManager {
private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp") private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp")
private val ANALYSIS_KEY = Key.create<LyngAnalysisResult>("lyng.analysis.cache") private val ANALYSIS_KEY = Key.create<LyngAnalysisResult>("lyng.analysis.cache")
private val implicitBuiltinNames = setOf("void") private val implicitBuiltinNames = setOf("void")
private val includeSymbolsDirective = Regex("""(?im)^\s*//\s*include\s+symbols\s*:\s*(.+?)\s*$""")
fun getMiniAst(file: PsiFile): MiniScript? = runReadAction { fun getMiniAst(file: PsiFile): MiniScript? = runReadAction {
getAnalysis(file)?.mini getAnalysis(file)?.mini
@ -44,8 +45,8 @@ object LyngAstManager {
fun getCombinedStamp(file: PsiFile): Long = runReadAction { fun getCombinedStamp(file: PsiFile): Long = runReadAction {
var combinedStamp = file.viewProvider.modificationStamp var combinedStamp = file.viewProvider.modificationStamp
if (!file.name.endsWith(".lyng.d")) { if (!file.name.endsWith(".lyng.d")) {
collectDeclarationFiles(file).forEach { df -> collectDeclarationFiles(file).forEach { symbolsFile ->
combinedStamp += df.viewProvider.modificationStamp combinedStamp += symbolsFile.viewProvider.modificationStamp
} }
} }
combinedStamp combinedStamp
@ -66,6 +67,22 @@ object LyngAstManager {
currentDir = currentDir.parentDirectory currentDir = currentDir.parentDirectory
} }
val includeSpecs = includeSymbolsDirective.findAll(file.viewProvider.contents)
.flatMap { it.groupValues[1].split(',').asSequence() }
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
val baseDir = file.virtualFile?.parent
if (baseDir != null) {
for (spec in includeSpecs) {
val included = baseDir.findFileByRelativePath(spec) ?: continue
if (included.path == file.virtualFile?.path) continue
if (seen.add(included.path)) {
psiManager.findFile(included)?.let { result.add(it) }
}
}
}
if (result.isNotEmpty()) return@runReadAction result if (result.isNotEmpty()) return@runReadAction result
// Fallback for virtual/light files without a stable parent chain (e.g., tests) // Fallback for virtual/light files without a stable parent chain (e.g., tests)

View File

@ -50,6 +50,18 @@ class LyngDefinitionFilesTest : BasePlatformTestCase() {
myFixture.addFileToProject("api.lyng.d", defs) myFixture.addFileToProject("api.lyng.d", defs)
} }
private fun addPlainSymbolsFile() {
val defs = """
/** Symbols exposed via include directive */
class PlainDeclared(val name: String) {
fun hello(): String = "ok"
}
fun plainTopFun(x: Int): Int = x + 2
""".trimIndent()
myFixture.addFileToProject("plain_symbols.lyng", defs)
}
fun test_CompletionsIncludeDefinitions() { fun test_CompletionsIncludeDefinitions() {
addDefinitionsFile() addDefinitionsFile()
enableCompletion() enableCompletion()
@ -136,4 +148,35 @@ class LyngDefinitionFilesTest : BasePlatformTestCase() {
val messages = analysis?.diagnostics?.map { it.message } ?: emptyList() val messages = analysis?.diagnostics?.map { it.message } ?: emptyList()
assertTrue("Should not report unresolved name for void, got=$messages", messages.none { it.contains("unresolved name: void") }) assertTrue("Should not report unresolved name for void, got=$messages", messages.none { it.contains("unresolved name: void") })
} }
fun test_CompletionsIncludePlainLyngViaDirective() {
addPlainSymbolsFile()
enableCompletion()
val code = """
// include symbols: plain_symbols.lyng
val v = plainTop<caret>
""".trimIndent()
myFixture.configureByText("main.lyng", code)
val text = myFixture.editor.document.text
val caret = myFixture.caretOffset
val analysis = LyngAstManager.getAnalysis(myFixture.file)
val engine = runBlocking { CompletionEngineLight.completeSuspend(text, caret, analysis?.mini, analysis?.binding).map { it.name } }
assertTrue("Expected plainTopFun from included .lyng; got=$engine", engine.contains("plainTopFun"))
}
fun test_DiagnosticsIgnorePlainLyngSymbolsViaDirective() {
addPlainSymbolsFile()
val code = """
// include symbols: plain_symbols.lyng
val x = plainTopFun(1)
val y = PlainDeclared("x")
y.hello()
""".trimIndent()
myFixture.configureByText("main.lyng", code)
val analysis = LyngAstManager.getAnalysis(myFixture.file)
val messages = analysis?.diagnostics?.map { it.message } ?: emptyList()
assertTrue("Should not report unresolved name for plainTopFun", messages.none { it.contains("unresolved name: plainTopFun") })
assertTrue("Should not report unresolved name for PlainDeclared", messages.none { it.contains("unresolved name: PlainDeclared") })
assertTrue("Should not report unresolved member for hello", messages.none { it.contains("unresolved member: hello") })
}
} }

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "1.5.0-SNAPSHOT" version = "1.5.2-SNAPSHOT"
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below

View File

@ -8,22 +8,22 @@ extern class IllegalArgumentException
extern class NotImplementedException extern class NotImplementedException
extern class Delegate extern class Delegate
extern class Iterable<T> { extern class Iterable<T> {
extern fun iterator(): Iterator<T> fun iterator(): Iterator<T>
extern fun forEach(action: (T)->void): void fun forEach(action: (T)->void): void
extern fun map<R>(transform: (T)->R): List<R> fun map<R>(transform: (T)->R): List<R>
extern fun toList(): List<T> fun toList(): List<T>
extern fun toImmutableList(): ImmutableList<T> fun toImmutableList(): ImmutableList<T>
extern val toSet: Set<T> val toSet: Set<T>
extern val toImmutableSet: ImmutableSet<T> val toImmutableSet: ImmutableSet<T>
extern val toMap: Map<Object,Object> val toMap: Map<Object,Object>
extern val toImmutableMap: ImmutableMap<Object,Object> val toImmutableMap: ImmutableMap<Object,Object>
} }
extern class Iterator<T> { extern class Iterator<T> {
extern fun hasNext(): Bool fun hasNext(): Bool
extern fun next(): T fun next(): T
extern fun cancelIteration(): void fun cancelIteration(): void
extern fun toList(): List<T> fun toList(): List<T>
} }
// Host-provided iterator wrapper for Kotlin collections. // Host-provided iterator wrapper for Kotlin collections.
@ -33,47 +33,47 @@ class KotlinIterator<T> : Iterator<T> {
} }
extern class Collection<T> : Iterable<T> { extern class Collection<T> : Iterable<T> {
extern val size: Int val size: Int
} }
extern class Array<T> : Collection<T> { extern class Array<T> : Collection<T> {
} }
extern class ImmutableList<T> : Array<T> { extern class ImmutableList<T> : Array<T> {
extern fun toMutable(): List<T> fun toMutable(): List<T>
} }
extern class List<T> : Array<T> { extern class List<T> : Array<T> {
extern fun add(value: T, more...): void fun add(value: T, more...): void
extern fun toImmutable(): ImmutableList<T> fun toImmutable(): ImmutableList<T>
} }
extern class RingBuffer<T> : Iterable<T> { extern class RingBuffer<T> : Iterable<T> {
extern val size: Int val size: Int
extern fun first(): T fun first(): T
extern fun add(value: T): void fun add(value: T): void
} }
extern class Set<T> : Collection<T> { extern class Set<T> : Collection<T> {
extern fun toImmutable(): ImmutableSet<T> fun toImmutable(): ImmutableSet<T>
} }
extern class ImmutableSet<T> : Collection<T> { extern class ImmutableSet<T> : Collection<T> {
extern fun toMutable(): Set<T> fun toMutable(): Set<T>
} }
extern class Map<K,V> : Collection<MapEntry<K,V>> { extern class Map<K,V> : Collection<MapEntry<K,V>> {
extern fun toImmutable(): ImmutableMap<K,V> fun toImmutable(): ImmutableMap<K,V>
} }
extern class ImmutableMap<K,V> : Collection<MapEntry<K,V>> { extern class ImmutableMap<K,V> : Collection<MapEntry<K,V>> {
extern fun getOrNull(key: K): V? fun getOrNull(key: K): V?
extern fun toMutable(): Map<K,V> fun toMutable(): Map<K,V>
} }
extern class MapEntry<K,V> : Array<Object> { extern class MapEntry<K,V> : Array<Object> {
extern val key: K val key: K
extern val value: V val value: V
} }
// Built-in math helpers (implemented in host runtime). // Built-in math helpers (implemented in host runtime).