Introduce typed semantic analysis pipeline

This commit is contained in:
pavel 2026-08-27 19:28:24 +02:00
commit f4cd4f4458
30 changed files with 2079 additions and 2381 deletions

View file

@ -707,7 +707,7 @@ fun main() {
}
}
func TestBuildDocumentStateChannelsAndSelectSemantics(t *testing.T) {
func TestBuildDocumentStateChannelSemantics(t *testing.T) {
text := strings.TrimSpace(`
package demo
@ -716,13 +716,8 @@ fun writer(ch: Channel<Int>) {
}
fun main() {
val ch = Channel<Int>()
runBlocking {
launch { writer(ch) }
select {
ch -> println(it)
}
}
val ch = Channel<Int>(1)
writer(ch)
val v = ch.read()
println(v)
}
@ -787,138 +782,3 @@ fun main() {
t.Fatal("expected removed worker syntax diagnostic")
}
}
func TestBuildDocumentStateTimerBuiltinsSemantics(t *testing.T) {
text := strings.TrimSpace(`
package demo
fun main() {
val once = after(1000)
val repeat = every(250)
select {
after(1000) -> println(it)
every(250) -> println("tick")
}
println(once)
println(repeat)
}
`)
state := buildDocumentState(text)
if state.program == nil {
t.Fatal("expected parsed program")
}
if len(state.diagnostics) != 0 {
t.Fatalf("expected no diagnostics, got %+v", state.diagnostics)
}
var onceDetail string
var repeatDetail string
for _, sym := range state.symbols {
if sym.Kind != symbolKindVariable {
continue
}
if sym.Name == "once" {
onceDetail = sym.Detail
}
if sym.Name == "repeat" {
repeatDetail = sym.Detail
}
}
if onceDetail != "val once: Channel<time.Time>" {
t.Fatalf("unexpected once detail: %q", onceDetail)
}
if repeatDetail != "val repeat: Channel<time.Time>" {
t.Fatalf("unexpected repeat detail: %q", repeatDetail)
}
}
func TestBuildDocumentStateEveryNonPositiveDiagnostics(t *testing.T) {
text := strings.TrimSpace(`
package demo
fun main() {
select {
every(0) -> println("x")
}
}
`)
state := buildDocumentState(text)
if state.program == nil {
t.Fatal("expected parsed program")
}
if len(state.diagnostics) == 0 {
t.Fatal("expected diagnostics")
}
found := false
for _, d := range state.diagnostics {
if strings.Contains(d.Message, "every(ms) requires ms > 0") {
found = true
break
}
}
if !found {
t.Fatalf("expected every(ms) diagnostic, got %+v", state.diagnostics)
}
}
func TestBuildDocumentStateAfterIntArgumentDiagnostics(t *testing.T) {
text := strings.TrimSpace(`
package demo
fun main() {
select {
after("1s") -> println("x")
}
}
`)
state := buildDocumentState(text)
if state.program == nil {
t.Fatal("expected parsed program")
}
found := false
for _, d := range state.diagnostics {
if strings.Contains(d.Message, "after(ms) expects an Int argument") {
found = true
break
}
}
if !found {
t.Fatalf("expected after int argument diagnostic, got %+v", state.diagnostics)
}
}
func TestHoverBuiltinEverySignature(t *testing.T) {
text := strings.TrimSpace(`
package demo
fun main() {
every(100)
}
`)
uri := "file:///tmp/demo.gt"
s := &server{
docs: map[string]documentState{
uri: buildDocumentState(text),
},
}
result := s.hover(uri, position{Line: 3, Character: 5})
if result == nil {
t.Fatal("expected hover result")
}
m, ok := result.(map[string]any)
if !ok {
t.Fatalf("unexpected hover type: %T", result)
}
contents, ok := m["contents"].(map[string]any)
if !ok {
t.Fatalf("unexpected hover contents: %+v", m)
}
value, _ := contents["value"].(string)
if !strings.Contains(value, "fun every(ms: Int): Channel<time.Time>") {
t.Fatalf("unexpected hover value: %q", value)
}
}