shader.go (19718B)
1 // Copyright 2020 The Ebiten Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package shader 16 17 import ( 18 "fmt" 19 "go/ast" 20 gconstant "go/constant" 21 "go/token" 22 "strings" 23 24 "github.com/hajimehoshi/ebiten/v2/internal/shaderir" 25 ) 26 27 type variable struct { 28 name string 29 typ shaderir.Type 30 forLoopCounter bool 31 } 32 33 type constant struct { 34 name string 35 typ shaderir.Type 36 ctyp shaderir.ConstType 37 value gconstant.Value 38 } 39 40 type function struct { 41 name string 42 block *block 43 44 ir shaderir.Func 45 } 46 47 type compileState struct { 48 fs *token.FileSet 49 50 vertexEntry string 51 fragmentEntry string 52 53 ir shaderir.Program 54 55 funcs []function 56 57 global block 58 59 varyingParsed bool 60 61 errs []string 62 } 63 64 func (cs *compileState) findFunction(name string) (int, bool) { 65 for i, f := range cs.funcs { 66 if f.name == name { 67 return i, true 68 } 69 } 70 return 0, false 71 } 72 73 func (cs *compileState) findUniformVariable(name string) (int, bool) { 74 for i, u := range cs.ir.UniformNames { 75 if u == name { 76 return i, true 77 } 78 } 79 return 0, false 80 } 81 82 type typ struct { 83 name string 84 ir shaderir.Type 85 } 86 87 type block struct { 88 types []typ 89 vars []variable 90 unusedVars map[int]token.Pos 91 consts []constant 92 pos token.Pos 93 outer *block 94 95 ir *shaderir.Block 96 } 97 98 func (b *block) totalLocalVariableNum() int { 99 c := len(b.vars) 100 if b.outer != nil { 101 c += b.outer.totalLocalVariableNum() 102 } 103 return c 104 } 105 106 func (b *block) addNamedLocalVariable(name string, typ shaderir.Type, pos token.Pos) { 107 b.vars = append(b.vars, variable{ 108 name: name, 109 typ: typ, 110 }) 111 if name == "_" { 112 return 113 } 114 idx := len(b.vars) - 1 115 if b.unusedVars == nil { 116 b.unusedVars = map[int]token.Pos{} 117 } 118 b.unusedVars[idx] = pos 119 } 120 121 func (b *block) findLocalVariable(name string, markLocalVariableUsed bool) (int, shaderir.Type, bool) { 122 if name == "" || name == "_" { 123 panic("shader: variable name must be non-empty and non-underscore") 124 } 125 126 idx := 0 127 for outer := b.outer; outer != nil; outer = outer.outer { 128 idx += len(outer.vars) 129 } 130 for i, v := range b.vars { 131 if v.name == name { 132 if markLocalVariableUsed { 133 delete(b.unusedVars, i) 134 } 135 return idx + i, v.typ, true 136 } 137 } 138 if b.outer != nil { 139 return b.outer.findLocalVariable(name, markLocalVariableUsed) 140 } 141 return 0, shaderir.Type{}, false 142 } 143 144 func (b *block) findLocalVariableByIndex(idx int) (shaderir.Type, bool) { 145 bs := []*block{b} 146 for outer := b.outer; outer != nil; outer = outer.outer { 147 bs = append(bs, outer) 148 } 149 for i := len(bs) - 1; i >= 0; i-- { 150 if len(bs[i].vars) <= idx { 151 idx -= len(bs[i].vars) 152 continue 153 } 154 return bs[i].vars[idx].typ, true 155 } 156 return shaderir.Type{}, false 157 } 158 159 func (b *block) findConstant(name string) (constant, bool) { 160 if name == "" || name == "_" { 161 panic("shader: constant name must be non-empty and non-underscore") 162 } 163 164 for _, c := range b.consts { 165 if c.name == name { 166 return c, true 167 } 168 } 169 if b.outer != nil { 170 return b.outer.findConstant(name) 171 } 172 173 return constant{}, false 174 } 175 176 type ParseError struct { 177 errs []string 178 } 179 180 func (p *ParseError) Error() string { 181 return strings.Join(p.errs, "\n") 182 } 183 184 func Compile(fs *token.FileSet, f *ast.File, vertexEntry, fragmentEntry string, textureNum int) (*shaderir.Program, error) { 185 s := &compileState{ 186 fs: fs, 187 vertexEntry: vertexEntry, 188 fragmentEntry: fragmentEntry, 189 } 190 s.global.ir = &shaderir.Block{} 191 s.parse(f) 192 193 if len(s.errs) > 0 { 194 return nil, &ParseError{s.errs} 195 } 196 197 // TODO: Resolve identifiers? 198 // TODO: Resolve constants 199 200 // TODO: Make a call graph and reorder the elements. 201 202 s.ir.TextureNum = textureNum 203 return &s.ir, nil 204 } 205 206 func (s *compileState) addError(pos token.Pos, str string) { 207 p := s.fs.Position(pos) 208 s.errs = append(s.errs, fmt.Sprintf("%s: %s", p, str)) 209 } 210 211 func (cs *compileState) parse(f *ast.File) { 212 // Parse GenDecl for global variables, and then parse functions. 213 for _, d := range f.Decls { 214 if _, ok := d.(*ast.FuncDecl); !ok { 215 ss, ok := cs.parseDecl(&cs.global, d) 216 if !ok { 217 return 218 } 219 cs.global.ir.Stmts = append(cs.global.ir.Stmts, ss...) 220 } 221 } 222 223 // Sort the uniform variable so that special variable starting with __ should come first. 224 var unames []string 225 var utypes []shaderir.Type 226 for i, u := range cs.ir.UniformNames { 227 if strings.HasPrefix(u, "__") { 228 unames = append(unames, u) 229 utypes = append(utypes, cs.ir.Uniforms[i]) 230 } 231 } 232 // TODO: Check len(unames) == graphics.PreservedUniformVariablesNum. Unfortunately this is not true on tests. 233 for i, u := range cs.ir.UniformNames { 234 if !strings.HasPrefix(u, "__") { 235 unames = append(unames, u) 236 utypes = append(utypes, cs.ir.Uniforms[i]) 237 } 238 } 239 cs.ir.UniformNames = unames 240 cs.ir.Uniforms = utypes 241 242 // Parse function names so that any other function call the others. 243 // The function data is provisional and will be updated soon. 244 for _, d := range f.Decls { 245 fd, ok := d.(*ast.FuncDecl) 246 if !ok { 247 continue 248 } 249 n := fd.Name.Name 250 if n == cs.vertexEntry { 251 continue 252 } 253 if n == cs.fragmentEntry { 254 continue 255 } 256 257 for _, f := range cs.funcs { 258 if f.name == n { 259 cs.addError(d.Pos(), fmt.Sprintf("redeclared function: %s", n)) 260 return 261 } 262 } 263 264 inParams, outParams := cs.parseFuncParams(&cs.global, fd) 265 var inT, outT []shaderir.Type 266 for _, v := range inParams { 267 inT = append(inT, v.typ) 268 } 269 for _, v := range outParams { 270 outT = append(outT, v.typ) 271 } 272 273 cs.funcs = append(cs.funcs, function{ 274 name: n, 275 ir: shaderir.Func{ 276 Index: len(cs.funcs), 277 InParams: inT, 278 OutParams: outT, 279 Block: &shaderir.Block{}, 280 }, 281 }) 282 } 283 284 // Parse functions. 285 for _, d := range f.Decls { 286 if _, ok := d.(*ast.FuncDecl); ok { 287 ss, ok := cs.parseDecl(&cs.global, d) 288 if !ok { 289 return 290 } 291 cs.global.ir.Stmts = append(cs.global.ir.Stmts, ss...) 292 } 293 } 294 295 if len(cs.errs) > 0 { 296 return 297 } 298 299 for _, f := range cs.funcs { 300 cs.ir.Funcs = append(cs.ir.Funcs, f.ir) 301 } 302 } 303 304 func (cs *compileState) parseDecl(b *block, d ast.Decl) ([]shaderir.Stmt, bool) { 305 var stmts []shaderir.Stmt 306 307 switch d := d.(type) { 308 case *ast.GenDecl: 309 switch d.Tok { 310 case token.TYPE: 311 // TODO: Parse other types 312 for _, s := range d.Specs { 313 s := s.(*ast.TypeSpec) 314 t, ok := cs.parseType(b, s.Type) 315 if !ok { 316 return nil, false 317 } 318 b.types = append(b.types, typ{ 319 name: s.Name.Name, 320 ir: t, 321 }) 322 } 323 case token.CONST: 324 for _, s := range d.Specs { 325 s := s.(*ast.ValueSpec) 326 cs, ok := cs.parseConstant(b, s) 327 if !ok { 328 return nil, false 329 } 330 b.consts = append(b.consts, cs...) 331 } 332 case token.VAR: 333 for _, s := range d.Specs { 334 s := s.(*ast.ValueSpec) 335 vs, inits, ss, ok := cs.parseVariable(b, s) 336 if !ok { 337 return nil, false 338 } 339 stmts = append(stmts, ss...) 340 if b == &cs.global { 341 // TODO: Should rhs be ignored? 342 for i, v := range vs { 343 if !strings.HasPrefix(v.name, "__") { 344 if v.name[0] < 'A' || 'Z' < v.name[0] { 345 cs.addError(s.Names[i].Pos(), fmt.Sprintf("global variables must be exposed: %s", v.name)) 346 } 347 } 348 cs.ir.UniformNames = append(cs.ir.UniformNames, v.name) 349 cs.ir.Uniforms = append(cs.ir.Uniforms, v.typ) 350 } 351 continue 352 } 353 354 // base must be obtained before adding the variables. 355 base := b.totalLocalVariableNum() 356 for _, v := range vs { 357 b.addNamedLocalVariable(v.name, v.typ, d.Pos()) 358 } 359 360 if len(inits) > 0 { 361 for i := range vs { 362 stmts = append(stmts, shaderir.Stmt{ 363 Type: shaderir.Assign, 364 Exprs: []shaderir.Expr{ 365 { 366 Type: shaderir.LocalVariable, 367 Index: base + i, 368 }, 369 inits[i], 370 }, 371 }) 372 } 373 } 374 } 375 case token.IMPORT: 376 cs.addError(d.Pos(), "import is forbidden") 377 default: 378 cs.addError(d.Pos(), "unexpected token") 379 } 380 case *ast.FuncDecl: 381 f, ok := cs.parseFunc(b, d) 382 if !ok { 383 return nil, false 384 } 385 if b != &cs.global { 386 cs.addError(d.Pos(), "non-global function is not implemented") 387 return nil, false 388 } 389 switch d.Name.Name { 390 case cs.vertexEntry: 391 cs.ir.VertexFunc.Block = f.ir.Block 392 case cs.fragmentEntry: 393 cs.ir.FragmentFunc.Block = f.ir.Block 394 default: 395 // The function is already registered for their names. 396 for i := range cs.funcs { 397 if cs.funcs[i].name == d.Name.Name { 398 // Index is already determined by the provisional parsing. 399 f.ir.Index = cs.funcs[i].ir.Index 400 cs.funcs[i] = f 401 break 402 } 403 } 404 } 405 default: 406 cs.addError(d.Pos(), "unexpected decl") 407 return nil, false 408 } 409 410 return stmts, true 411 } 412 413 // functionReturnTypes returns the original returning value types, if the given expression is call. 414 // 415 // Note that parseExpr returns the returning types for IR, not the original function. 416 func (cs *compileState) functionReturnTypes(block *block, expr ast.Expr) ([]shaderir.Type, bool) { 417 call, ok := expr.(*ast.CallExpr) 418 if !ok { 419 return nil, false 420 } 421 422 ident, ok := call.Fun.(*ast.Ident) 423 if !ok { 424 return nil, false 425 } 426 427 for _, f := range cs.funcs { 428 if f.name == ident.Name { 429 // TODO: Is it correct to combine out-params and return param? 430 ts := f.ir.OutParams 431 if f.ir.Return.Main != shaderir.None { 432 ts = append(ts, f.ir.Return) 433 } 434 return ts, true 435 } 436 } 437 return nil, false 438 } 439 440 func (s *compileState) parseVariable(block *block, vs *ast.ValueSpec) ([]variable, []shaderir.Expr, []shaderir.Stmt, bool) { 441 if len(vs.Names) != len(vs.Values) && len(vs.Values) != 1 && len(vs.Values) != 0 { 442 s.addError(vs.Pos(), fmt.Sprintf("the numbers of lhs and rhs don't match")) 443 return nil, nil, nil, false 444 } 445 446 var declt shaderir.Type 447 if vs.Type != nil { 448 var ok bool 449 declt, ok = s.parseType(block, vs.Type) 450 if !ok { 451 return nil, nil, nil, false 452 } 453 } 454 455 var ( 456 vars []variable 457 inits []shaderir.Expr 458 stmts []shaderir.Stmt 459 ) 460 461 // These variables are used only in multiple-value context. 462 var inittypes []shaderir.Type 463 var initexprs []shaderir.Expr 464 465 for i, n := range vs.Names { 466 t := declt 467 switch { 468 case len(vs.Values) == 0: 469 // No initialization 470 471 case len(vs.Names) == len(vs.Values): 472 // Single-value context 473 474 init := vs.Values[i] 475 476 es, origts, ss, ok := s.parseExpr(block, init, true) 477 if !ok { 478 return nil, nil, nil, false 479 } 480 481 if t.Main == shaderir.None { 482 ts, ok := s.functionReturnTypes(block, init) 483 if !ok { 484 ts = origts 485 } 486 if len(ts) > 1 { 487 s.addError(vs.Pos(), fmt.Sprintf("the numbers of lhs and rhs don't match")) 488 } 489 t = ts[0] 490 } 491 492 if es[0].Type == shaderir.NumberExpr { 493 switch t.Main { 494 case shaderir.Int: 495 es[0].ConstType = shaderir.ConstTypeInt 496 case shaderir.Float: 497 es[0].ConstType = shaderir.ConstTypeFloat 498 } 499 } 500 501 inits = append(inits, es...) 502 stmts = append(stmts, ss...) 503 504 default: 505 // Multiple-value context 506 507 if i == 0 { 508 init := vs.Values[0] 509 510 var ss []shaderir.Stmt 511 var ok bool 512 initexprs, inittypes, ss, ok = s.parseExpr(block, init, true) 513 if !ok { 514 return nil, nil, nil, false 515 } 516 stmts = append(stmts, ss...) 517 518 if t.Main == shaderir.None { 519 ts, ok := s.functionReturnTypes(block, init) 520 if ok { 521 inittypes = ts 522 } 523 if len(ts) != len(vs.Names) { 524 s.addError(vs.Pos(), fmt.Sprintf("the numbers of lhs and rhs don't match")) 525 continue 526 } 527 } 528 } 529 if len(inittypes) > 0 { 530 t = inittypes[i] 531 } 532 533 // Add the same initexprs for each variable. 534 inits = append(inits, initexprs...) 535 } 536 537 name := n.Name 538 for _, v := range append(block.vars, vars...) { 539 if v.name == name { 540 s.addError(vs.Pos(), fmt.Sprintf("duplicated local variable name: %s", name)) 541 return nil, nil, nil, false 542 } 543 } 544 for _, c := range block.consts { 545 if c.name == name { 546 s.addError(vs.Pos(), fmt.Sprintf("duplicated local constant/variable name: %s", name)) 547 return nil, nil, nil, false 548 } 549 } 550 vars = append(vars, variable{ 551 name: name, 552 typ: t, 553 }) 554 } 555 556 return vars, inits, stmts, true 557 } 558 559 func (s *compileState) parseConstant(block *block, vs *ast.ValueSpec) ([]constant, bool) { 560 var t shaderir.Type 561 if vs.Type != nil { 562 var ok bool 563 t, ok = s.parseType(block, vs.Type) 564 if !ok { 565 return nil, false 566 } 567 } 568 569 var cs []constant 570 for i, n := range vs.Names { 571 name := n.Name 572 for _, c := range block.consts { 573 if c.name == name { 574 s.addError(vs.Pos(), fmt.Sprintf("duplicated local constant name: %s", name)) 575 return nil, false 576 } 577 } 578 for _, v := range block.vars { 579 if v.name == name { 580 s.addError(vs.Pos(), fmt.Sprintf("duplicated local constant/variable name: %s", name)) 581 return nil, false 582 } 583 } 584 585 es, ts, ss, ok := s.parseExpr(block, vs.Values[i], false) 586 if !ok { 587 return nil, false 588 } 589 if len(ss) > 0 { 590 s.addError(vs.Pos(), fmt.Sprintf("invalid constant expression: %s", name)) 591 return nil, false 592 } 593 if len(ts) != 1 || len(es) != 1 { 594 s.addError(vs.Pos(), fmt.Sprintf("invalid constant expression: %s", n)) 595 return nil, false 596 } 597 if es[0].Type != shaderir.NumberExpr { 598 s.addError(vs.Pos(), fmt.Sprintf("constant expresion must be a number but not: %s", n)) 599 return nil, false 600 } 601 cs = append(cs, constant{ 602 name: name, 603 typ: t, 604 ctyp: es[0].ConstType, 605 value: es[0].Const, 606 }) 607 } 608 return cs, true 609 } 610 611 func (cs *compileState) parseFuncParams(block *block, d *ast.FuncDecl) (in, out []variable) { 612 for _, f := range d.Type.Params.List { 613 t, ok := cs.parseType(block, f.Type) 614 if !ok { 615 return 616 } 617 for _, n := range f.Names { 618 in = append(in, variable{ 619 name: n.Name, 620 typ: t, 621 }) 622 } 623 } 624 625 if d.Type.Results == nil { 626 return 627 } 628 629 for _, f := range d.Type.Results.List { 630 t, ok := cs.parseType(block, f.Type) 631 if !ok { 632 return 633 } 634 if len(f.Names) == 0 { 635 out = append(out, variable{ 636 name: "", 637 typ: t, 638 }) 639 } else { 640 for _, n := range f.Names { 641 out = append(out, variable{ 642 name: n.Name, 643 typ: t, 644 }) 645 } 646 } 647 } 648 return 649 } 650 651 func (cs *compileState) parseFunc(block *block, d *ast.FuncDecl) (function, bool) { 652 if d.Name == nil { 653 cs.addError(d.Pos(), "function must have a name") 654 return function{}, false 655 } 656 if d.Name.Name == "init" { 657 cs.addError(d.Pos(), "init function is not implemented") 658 return function{}, false 659 } 660 if d.Body == nil { 661 cs.addError(d.Pos(), "function must have a body") 662 return function{}, false 663 } 664 665 inParams, outParams := cs.parseFuncParams(block, d) 666 667 checkVaryings := func(vs []variable) { 668 if len(cs.ir.Varyings) != len(vs) { 669 cs.addError(d.Pos(), fmt.Sprintf("the number of vertex entry point's returning values and the number of framgent entry point's params must be the same")) 670 return 671 } 672 for i, t := range cs.ir.Varyings { 673 if t.Main != vs[i].typ.Main { 674 cs.addError(d.Pos(), fmt.Sprintf("vertex entry point's returning value types and framgent entry point's param types must match")) 675 } 676 } 677 } 678 679 if block == &cs.global { 680 switch d.Name.Name { 681 case cs.vertexEntry: 682 for _, v := range inParams { 683 cs.ir.Attributes = append(cs.ir.Attributes, v.typ) 684 } 685 686 // The first out-param is treated as gl_Position in GLSL. 687 if len(outParams) == 0 { 688 cs.addError(d.Pos(), fmt.Sprintf("vertex entry point must have at least one returning vec4 value for a position")) 689 return function{}, false 690 } 691 if outParams[0].typ.Main != shaderir.Vec4 { 692 cs.addError(d.Pos(), fmt.Sprintf("vertex entry point must have at least one returning vec4 value for a position")) 693 return function{}, false 694 } 695 696 if cs.varyingParsed { 697 checkVaryings(outParams[1:]) 698 } else { 699 for _, v := range outParams[1:] { 700 // TODO: Check that these params are not arrays or structs 701 cs.ir.Varyings = append(cs.ir.Varyings, v.typ) 702 } 703 } 704 cs.varyingParsed = true 705 case cs.fragmentEntry: 706 if len(inParams) == 0 { 707 cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have at least one vec4 parameter for a position")) 708 return function{}, false 709 } 710 if inParams[0].typ.Main != shaderir.Vec4 { 711 cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have at least one vec4 parameter for a position")) 712 return function{}, false 713 } 714 715 if len(outParams) != 1 { 716 cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have one returning vec4 value for a color")) 717 return function{}, false 718 } 719 if outParams[0].typ.Main != shaderir.Vec4 { 720 cs.addError(d.Pos(), fmt.Sprintf("fragment entry point must have one returning vec4 value for a color")) 721 return function{}, false 722 } 723 724 if cs.varyingParsed { 725 checkVaryings(inParams[1:]) 726 } else { 727 for _, v := range inParams[1:] { 728 cs.ir.Varyings = append(cs.ir.Varyings, v.typ) 729 } 730 } 731 cs.varyingParsed = true 732 } 733 } 734 735 b, ok := cs.parseBlock(block, d.Name.Name, d.Body.List, inParams, outParams, true) 736 if !ok { 737 return function{}, false 738 } 739 740 if len(outParams) > 0 { 741 var hasReturn func(stmts []shaderir.Stmt) bool 742 hasReturn = func(stmts []shaderir.Stmt) bool { 743 for _, stmt := range stmts { 744 if stmt.Type == shaderir.Return { 745 return true 746 } 747 for _, b := range stmt.Blocks { 748 if hasReturn(b.Stmts) { 749 return true 750 } 751 } 752 } 753 return false 754 } 755 756 if !hasReturn(b.ir.Stmts) { 757 cs.addError(d.Pos(), fmt.Sprintf("function %s must have a return statement but not", d.Name)) 758 return function{}, false 759 } 760 } 761 762 var inT, outT []shaderir.Type 763 for _, v := range inParams { 764 inT = append(inT, v.typ) 765 } 766 for _, v := range outParams { 767 outT = append(outT, v.typ) 768 } 769 770 return function{ 771 name: d.Name.Name, 772 block: b, 773 ir: shaderir.Func{ 774 InParams: inT, 775 OutParams: outT, 776 Block: b.ir, 777 }, 778 }, true 779 } 780 781 func (cs *compileState) parseBlock(outer *block, fname string, stmts []ast.Stmt, inParams, outParams []variable, checkLocalVariableUsage bool) (*block, bool) { 782 var vars []variable 783 if outer == &cs.global { 784 vars = make([]variable, 0, len(inParams)+len(outParams)) 785 vars = append(vars, inParams...) 786 vars = append(vars, outParams...) 787 } 788 789 var offset int 790 for b := outer; b != nil; b = b.outer { 791 offset += len(b.vars) 792 } 793 if outer == &cs.global { 794 offset += len(inParams) + len(outParams) 795 } 796 797 block := &block{ 798 vars: vars, 799 outer: outer, 800 ir: &shaderir.Block{ 801 LocalVarIndexOffset: offset, 802 }, 803 } 804 805 defer func() { 806 var offset int 807 if outer == &cs.global { 808 offset = len(inParams) + len(outParams) 809 } 810 for _, v := range block.vars[offset:] { 811 if v.forLoopCounter { 812 block.ir.LocalVars = append(block.ir.LocalVars, shaderir.Type{}) 813 continue 814 } 815 block.ir.LocalVars = append(block.ir.LocalVars, v.typ) 816 } 817 }() 818 819 if outer.outer == nil && len(outParams) > 0 && outParams[0].name != "" { 820 for i := range outParams { 821 block.ir.Stmts = append(block.ir.Stmts, shaderir.Stmt{ 822 Type: shaderir.Init, 823 InitIndex: len(inParams) + i, 824 }) 825 } 826 } 827 828 for _, stmt := range stmts { 829 ss, ok := cs.parseStmt(block, fname, stmt, inParams, outParams) 830 if !ok { 831 return nil, false 832 } 833 block.ir.Stmts = append(block.ir.Stmts, ss...) 834 } 835 836 if checkLocalVariableUsage && len(block.unusedVars) > 0 { 837 for idx, pos := range block.unusedVars { 838 cs.addError(pos, fmt.Sprintf("local variable %s is not used", block.vars[idx].name)) 839 } 840 return nil, false 841 } 842 843 return block, true 844 }