fixed few bugs in compiler and plugin

This commit is contained in:
Sergey Chernov 2026-03-15 02:53:33 +03:00
parent 37d093817e
commit e447c778ed
10 changed files with 243 additions and 45 deletions

View File

@ -88,9 +88,11 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
// Imports: each segment as namespace/path
mini?.imports?.forEach { imp ->
imp.segments.forEach { seg ->
val start = analysis.source.offsetOf(seg.range.start)
val end = analysis.source.offsetOf(seg.range.end)
putRange(start, end, LyngHighlighterColors.NAMESPACE)
if (seg.range.start.source === analysis.source && seg.range.end.source === analysis.source) {
val start = analysis.source.offsetOf(seg.range.start)
val end = analysis.source.offsetOf(seg.range.end)
putRange(start, end, LyngHighlighterColors.NAMESPACE)
}
}
}

View File

@ -35,7 +35,7 @@ class LyngLexer : LexerBase() {
"fun", "val", "var", "class", "interface", "type", "import", "as",
"abstract", "closed", "override", "static", "extern", "open", "private", "protected",
"if", "else", "for", "while", "return", "true", "false", "null",
"when", "in", "is", "break", "continue", "try", "catch", "finally",
"when", "in", "is", "break", "continue", "try", "catch", "finally", "void",
"get", "set", "object", "enum", "init", "by", "step", "property", "constructor"
)

View File

@ -26,23 +26,16 @@ import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.binding.BindingSnapshot
import net.sergeych.lyng.miniast.BuiltinDocRegistry
import net.sergeych.lyng.miniast.DocLookupUtils
import net.sergeych.lyng.miniast.MiniEnumDecl
import net.sergeych.lyng.miniast.MiniRange
import net.sergeych.lyng.miniast.MiniScript
import net.sergeych.lyng.tools.IdeLenientImportProvider
import net.sergeych.lyng.tools.LyngAnalysisRequest
import net.sergeych.lyng.tools.LyngAnalysisResult
import net.sergeych.lyng.tools.LyngDiagnostic
import net.sergeych.lyng.tools.LyngLanguageTools
import net.sergeych.lyng.idea.LyngFileType
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.tools.*
object LyngAstManager {
private val MINI_KEY = Key.create<MiniScript>("lyng.mini.cache")
private val BINDING_KEY = Key.create<BindingSnapshot>("lyng.binding.cache")
private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp")
private val ANALYSIS_KEY = Key.create<LyngAnalysisResult>("lyng.analysis.cache")
private val implicitBuiltinNames = setOf("void")
fun getMiniAst(file: PsiFile): MiniScript? = runReadAction {
getAnalysis(file)?.mini
@ -217,7 +210,7 @@ object LyngAstManager {
val msg = diag.message
if (msg.startsWith("unresolved name: ")) {
val name = msg.removePrefix("unresolved name: ").trim()
name in declaredTopLevel || name in builtinTopLevel
name in declaredTopLevel || name in builtinTopLevel || name in implicitBuiltinNames
} else if (msg.startsWith("unresolved member: ")) {
val name = msg.removePrefix("unresolved member: ").trim()
val range = diag.range

View File

@ -124,4 +124,16 @@ class LyngDefinitionFilesTest : BasePlatformTestCase() {
assertTrue("Should not report unresolved name for Declared", messages.none { it.contains("unresolved name: Declared") })
assertTrue("Should not report unresolved member for greet", messages.none { it.contains("unresolved member: greet") })
}
fun test_DiagnosticsDoNotReportVoidAsUnresolvedName() {
val code = """
fun f(): void {
return void
}
""".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 void, got=$messages", messages.none { it.contains("unresolved name: void") })
}
}

View File

@ -176,6 +176,8 @@ class Compiler(
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
mutableMapOf()
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
private val classMethodReturnTypeByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
private val classMethodReturnTypeDeclByName: MutableMap<String, MutableMap<String, TypeDecl>> = mutableMapOf()
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
@ -2614,7 +2616,34 @@ class Compiler(
}
Token.Type.NOT -> {
if (operand != null) throw ScriptError(t.pos, "unexpected operator not '!' ")
if (operand != null) {
val save = cc.savePos()
val next = cc.next()
if (next.type == Token.Type.NOT) {
val operandRef = operand
val receiverClass = resolveReceiverClassForMember(operandRef)
val inferredType = resolveReceiverTypeDecl(operandRef)
?: receiverClass?.let { TypeDecl.Simple(it.className, false) }
if (inferredType == null) {
operand = operandRef
continue
}
if (inferredType == TypeDecl.TypeAny || inferredType == TypeDecl.TypeNullableAny) {
operand = operandRef
continue
}
val nonNullType = makeTypeDeclNonNullable(inferredType)
operand = CastRef(
operandRef,
TypeDeclRef(nonNullType, t.pos),
isNullable = false,
atPos = t.pos
)
continue
}
cc.restorePos(save)
throw ScriptError(t.pos, "unexpected operator not '!' ")
}
val op = parseTerm() ?: throw ScriptError(t.pos, "Expecting expression")
operand = UnaryOpRef(UnaryOp.NOT, op)
}
@ -3821,6 +3850,21 @@ class Compiler(
}
}
private fun makeTypeDeclNonNullable(type: TypeDecl): TypeDecl {
if (!type.isNullable) return type
return when (type) {
TypeDecl.TypeAny -> type
TypeDecl.TypeNullableAny -> TypeDecl.TypeAny
is TypeDecl.Function -> type.copy(nullable = false)
is TypeDecl.Ellipsis -> type.copy(nullable = false)
is TypeDecl.TypeVar -> type.copy(nullable = false)
is TypeDecl.Union -> type.copy(nullable = false)
is TypeDecl.Intersection -> type.copy(nullable = false)
is TypeDecl.Simple -> TypeDecl.Simple(type.name, false)
is TypeDecl.Generic -> TypeDecl.Generic(type.name, type.args, false)
}
}
private fun makeMiniTypeNullable(type: MiniTypeRef): MiniTypeRef {
return when (type) {
is MiniTypeName -> type.copy(nullable = true)
@ -4491,6 +4535,48 @@ class Compiler(
is TypeDecl.Intersection -> "I:${type.options.joinToString("&") { typeDeclKey(it) }}"
}
private fun classMethodReturnTypeDecl(targetClass: ObjClass?, name: String): TypeDecl? {
if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return TypeDecl.TypeAny
val member = targetClass.getInstanceMemberOrNull(name, includeAbstract = true)
val declaringName = member?.declaringClass?.className
if (declaringName != null) {
classMethodReturnTypeDeclByName[declaringName]?.get(name)?.let { return it }
classMethodReturnTypeByName[declaringName]?.get(name)?.let {
return TypeDecl.Simple(it.className, false)
}
}
classMethodReturnTypeDeclByName[targetClass.className]?.get(name)?.let { return it }
classMethodReturnTypeByName[targetClass.className]?.get(name)?.let {
return TypeDecl.Simple(it.className, false)
}
member?.typeDecl?.let { declaredType ->
if (declaredType is TypeDecl.Function) return declaredType.returnType
return declaredType
}
return null
}
private fun classMethodReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return ObjDynamic.type
classMethodReturnTypeDecl(targetClass, name)?.let { declared ->
resolveTypeDeclObjClass(declared)?.let { return it }
if (declared is TypeDecl.TypeVar) return Obj.rootObjectType
}
val member = targetClass.getInstanceMemberOrNull(name, includeAbstract = true)
val declaringName = member?.declaringClass?.className
if (declaringName != null) {
classMethodReturnTypeByName[declaringName]?.get(name)?.let { return it }
}
classMethodReturnTypeByName[targetClass.className]?.get(name)?.let { return it }
val declaredType = member?.typeDecl
if (declaredType is TypeDecl.Function) {
resolveTypeDeclObjClass(declaredType.returnType)?.let { return it }
}
return null
}
private fun inferObjClassFromRef(ref: ObjRef): ObjClass? = when (ref) {
is ConstRef -> ref.constValue as? ObjClass ?: ref.constValue.objClass
is LocalVarRef -> nameObjClass[ref.name] ?: resolveClassByName(ref.name)
@ -4511,6 +4597,11 @@ class Compiler(
is RangeRef -> ObjRange.type
is ClassOperatorRef -> ObjClassType
is CastRef -> resolveTypeRefClass(ref.castTypeRef())
is IndexRef -> {
val targetClass = resolveReceiverClassForMember(ref.targetRef)
classMethodReturnClass(targetClass, "getAt")
?: inferFieldReturnClass(targetClass, "getAt")
}
else -> null
}
@ -4547,6 +4638,12 @@ class Compiler(
else -> TypeDecl.TypeVar("${typeDeclName(targetDecl)}.${ref.name}", false)
}
}
is IndexRef -> {
val targetDecl = resolveReceiverTypeDecl(ref.targetRef)
inferMethodCallReturnTypeDecl("getAt", targetDecl)?.let { return it }
val targetClass = resolveReceiverClassForMember(ref.targetRef)
classMethodReturnTypeDecl(targetClass, "getAt")
}
is MethodCallRef -> methodReturnTypeDeclByRef[ref]
is CallRef -> callReturnTypeDeclByRef[ref]
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
@ -4616,6 +4713,12 @@ class Compiler(
val targetClass = resolveReceiverClassForMember(ref.target)
inferFieldReturnClass(targetClass, ref.name)
}
is IndexRef -> {
val targetClass = resolveReceiverClassForMember(ref.targetRef)
classMethodReturnClass(targetClass, "getAt")
?: inferFieldReturnClass(targetClass, "getAt")
?: inferMethodCallReturnClass("getAt")
}
else -> null
}
}
@ -5102,6 +5205,7 @@ class Compiler(
}
private fun resolveTypeRefClass(ref: ObjRef): ObjClass? = when (ref) {
is TypeDeclRef -> resolveTypeDeclObjClass(ref.decl())
is ConstRef -> ref.constValue as? ObjClass
is LocalSlotRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
is LocalVarRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
@ -7885,6 +7989,23 @@ class Compiler(
val rawFnStatements = parsedFnStatements?.let { unwrapBytecodeDeep(it) }
val inferredReturnClass = returnTypeDecl?.let { resolveTypeDeclObjClass(it) }
?: inferReturnClassFromStatement(rawFnStatements)
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
val ownerClassName = (parentContext as? CodeContext.ClassBody)?.name
if (ownerClassName != null) {
val returnDecl = returnTypeDecl
?: inferredReturnClass?.let { TypeDecl.Simple(it.className, false) }
if (returnDecl != null) {
classMethodReturnTypeDeclByName
.getOrPut(ownerClassName) { mutableMapOf() }[name] = returnDecl
resolveTypeDeclObjClass(returnDecl)?.let { returnClass ->
classMethodReturnTypeByName
.getOrPut(ownerClassName) { mutableMapOf() }[name] = returnClass
classFieldTypesByName
.getOrPut(ownerClassName) { mutableMapOf() }[name] = returnClass
}
}
}
}
if (declKind != SymbolKind.MEMBER && inferredReturnClass != null) {
callableReturnTypeByName[name] = inferredReturnClass
val slotLoc = lookupSlotLocation(name, includeModule = true)

View File

@ -46,7 +46,7 @@ private val fallbackKeywordIds = setOf(
"private", "protected", "static", "open", "extern", "init", "get", "set", "by", "step",
// control flow and misc
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset"
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset", "void"
)
/** Maps lexer token type (and sometimes value) to a [HighlightKind]. */

View File

@ -179,6 +179,7 @@ object LyngLanguageTools {
val source = analysis.source
val out = ArrayList<LyngSemanticSpan>(128)
val covered = HashSet<Pair<Int, Int>>()
fun isCurrentSource(pos: Pos): Boolean = pos.source === source
fun addRange(start: Int, end: Int, kind: LyngSemanticKind) {
if (start < 0 || end <= start || end > analysis.text.length) return
@ -187,6 +188,7 @@ object LyngLanguageTools {
}
fun addName(pos: Pos, name: String, kind: LyngSemanticKind) {
if (!isCurrentSource(pos)) return
val s = source.offsetOf(pos)
addRange(s, s + name.length, kind)
}
@ -206,7 +208,9 @@ object LyngLanguageTools {
addTypeSegments(t.returnType)
}
is MiniTypeVar -> {
addRange(source.offsetOf(t.range.start), source.offsetOf(t.range.end), LyngSemanticKind.TypeRef)
if (isCurrentSource(t.range.start) && isCurrentSource(t.range.end)) {
addRange(source.offsetOf(t.range.start), source.offsetOf(t.range.end), LyngSemanticKind.TypeRef)
}
}
is MiniTypeUnion -> {
t.options.forEach { addTypeSegments(it) }
@ -262,7 +266,9 @@ object LyngLanguageTools {
mini.imports.forEach { imp ->
imp.segments.forEach { seg ->
addRange(source.offsetOf(seg.range.start), source.offsetOf(seg.range.end), LyngSemanticKind.TypeRef)
if (isCurrentSource(seg.range.start) && isCurrentSource(seg.range.end)) {
addRange(source.offsetOf(seg.range.start), source.offsetOf(seg.range.end), LyngSemanticKind.TypeRef)
}
}
}

View File

@ -565,19 +565,6 @@ class TypesTest {
""".trimIndent())
}
// @Test
// fun testNullableGenericTypes() = runTest {
// eval("""
// fun t<T>(): String =
// when(T) {
// is Object -> "%s is Object"(T::class.name)
// else -> throw "It should not happen"
// }
// assert( Int is Object)
// assertEquals( t<Int>(), "Class is Object")
// """.trimIndent())
// }
@Test fun testIndexer() = runTest {
eval("""
class Greeter {
@ -596,6 +583,38 @@ class TypesTest {
""".trimIndent())
}
@Test fun testIndexer2() = runTest {
eval("""
class Foo(bar)
class Greeter {
override fun getAt(name): Foo = Foo("Hello, %s!"(name))
}
assertEquals("Hello, Bob!",Greeter()["Bob"].bar)
val g = Greeter()
assertEquals("Hello, Bob!",g["Bob"].bar)
// it should work with objects too:
object Polite {
override fun getAt(name): Foo? = Foo("How do you do, %s?"(name))
}
assertEquals("How do you do, Bob?",Polite["Bob"].bar)
assertEquals("How do you do, Bob?",Polite["Bob"]?.bar)
assertEquals("How do you do, Bob?",Polite["Bob"]!!.bar)
class Greeter2 {
override fun getAt(name): Foo? = Foo("How do you do, %s?"(name))
}
val g2 = Greeter2()
assertEquals("How do you do, Bob?",g2["Bob"]?.bar)
val g2v: Foo = g2["Bob"]!!
assertEquals("How do you do, Bob?",g2v.bar)
assertEquals("How do you do, Bob?",Greeter2()["Bob"].bar)
""".trimIndent())
}
// @Test fun nonTrivialOperatorsTest() = runTest {
// val s = Script.newScope()
// s.eval("""

View File

@ -18,8 +18,9 @@
package net.sergeych.lyng.tools
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.miniast.MiniClassDecl
import net.sergeych.lyng.miniast.MiniMemberTypeAliasDecl
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Source
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.stdlib_included.rootLyng
import kotlin.test.Test
import kotlin.test.assertEquals
@ -127,4 +128,48 @@ class LyngLanguageToolsTest {
val dis = LyngLanguageTools.disassembleSymbol(code, "add")
assertTrue(!dis.contains("not a compiled body"), "Disassembly should be produced, got: $dis")
}
@Test
fun languageTools_semanticHighlights_ignore_foreign_sources() {
val localSource = Source("local.lyng", "val x = 1")
val foreignSource = Source("defs.lyng.d", "val y = 2")
val localStart = Pos(localSource, 0, 0)
val foreignStart = Pos(foreignSource, 0, 0)
val mini = MiniScript(
range = MiniRange(localStart, localStart),
declarations = mutableListOf(
MiniValDecl(
range = MiniRange(foreignStart, foreignStart),
name = "y",
mutable = false,
type = null,
initRange = null,
doc = null,
nameStart = foreignStart
)
),
imports = mutableListOf(
MiniImport(
range = MiniRange(foreignStart, foreignStart),
segments = listOf(
MiniImport.Segment("defs", MiniRange(foreignStart, foreignStart))
)
)
)
)
val analysis = LyngAnalysisResult(
source = localSource,
text = localSource.text,
mini = mini,
binding = null,
resolution = null,
importedModules = emptyList(),
diagnostics = emptyList(),
lexicalHighlights = emptyList()
)
val spans = LyngLanguageTools.semanticHighlights(analysis)
assertTrue(spans.isEmpty(), "Semantic spans should ignore positions from foreign sources, got $spans")
}
}

View File

@ -1,6 +1,6 @@
package lyng.stdlib
extern fun flow(builder: FlowBuilder.()->Void): Flow
extern fun flow(builder: FlowBuilder.()->void): Flow
/* Built-in exception type. */
extern class Exception
@ -9,7 +9,7 @@ extern class NotImplementedException
extern class Delegate
extern class Iterable<T> {
fun iterator(): Iterator<T>
fun forEach(action: (T)->Void): Void
fun forEach(action: (T)->void): void
fun map<R>(transform: (T)->R): List<R>
fun toList(): List<T>
fun toImmutableList(): ImmutableList<T>
@ -22,7 +22,7 @@ extern class Iterable<T> {
extern class Iterator<T> {
fun hasNext(): Bool
fun next(): T
fun cancelIteration(): Void
fun cancelIteration(): void
fun toList(): List<T>
}
@ -44,14 +44,14 @@ extern class ImmutableList<T> : Array<T> {
}
extern class List<T> : Array<T> {
fun add(value: T, more...): Void
fun add(value: T, more...): void
fun toImmutable(): ImmutableList<T>
}
extern class RingBuffer<T> : Iterable<T> {
val size: Int
fun first(): T
fun add(value: T): Void
fun add(value: T): void
}
extern class Set<T> : Collection<T> {
@ -337,17 +337,17 @@ override fun List<T>.toString() {
}
/* Sort list in-place by key selector. */
fun List<T>.sortBy<R>(predicate: (T)->R): Void {
fun List<T>.sortBy<R>(predicate: (T)->R): void {
sortWith { a, b -> predicate(a) <=> predicate(b) }
}
/* Sort list in-place by natural order. */
fun List<T>.sort(): Void {
fun List<T>.sort(): void {
sortWith { a, b -> a <=> b }
}
/* Print this exception and its stack trace to standard output. */
fun Exception.printStackTrace(): Void {
fun Exception.printStackTrace(): void {
println(this)
for( entry in stackTrace ) {
println("\tat "+entry.toString())
@ -357,7 +357,7 @@ fun Exception.printStackTrace(): Void {
/* Compile this string into a regular expression. */
val String.re: Regex get() = Regex(this)
fun TODO(message: Object?=null): Void {
fun TODO(message: Object?=null): void {
throw "not implemented"
}
@ -376,12 +376,12 @@ enum DelegateAccess {
Implementing this interface is optional as Lyng uses dynamic dispatch,
but it is recommended for documentation and clarity.
*/
interface Delegate<T,ThisRefType=Void> {
interface Delegate<T,ThisRefType=void> {
/* Called when a delegated 'val' or 'var' is read. */
fun getValue(thisRef: ThisRefType, name: String): T = TODO("delegate getter is not implemented")
/* Called when a delegated 'var' is written. */
fun setValue(thisRef: ThisRefType, name: String, newValue: T): Void = TODO("delegate setter is not implemented")
fun setValue(thisRef: ThisRefType, name: String, newValue: T): void = TODO("delegate setter is not implemented")
/* Called when a delegated function is invoked. */
fun invoke(thisRef: ThisRefType, name: String, args...): Object = TODO("delegate invoke is not implemented")