M .golangci.toml => .golangci.toml +3 -0
@@ 51,5 51,8 @@
# pkg/codegen
"`testUpdateCodegenTests` is a global variable",
+
+ # pkg/typecheck
+ "`testUpdateScopesTests` is a global variable",
]
M go.mod => go.mod +1 -0
@@ 8,6 8,7 @@ require (
github.com/gorilla/schema v1.1.0
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/kelseyhightower/envconfig v1.4.0
+ github.com/kr/pretty v0.1.0
github.com/kylelemons/godebug v1.1.0
github.com/mna/httpparms v0.0.0-20160806173130-30c778d9c13f
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect
M go.sum => go.sum +2 -0
@@ 33,8 33,10 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
A pkg/typecheck/testdata/scopes/empty.snow => pkg/typecheck/testdata/scopes/empty.snow +0 -0
A pkg/typecheck/testdata/scopes/empty.snow.err => pkg/typecheck/testdata/scopes/empty.snow.err +0 -0
A pkg/typecheck/testdata/scopes/empty.snow.want => pkg/typecheck/testdata/scopes/empty.snow.want +4 -0
@@ 0,0 1,4 @@
+<nil> {
+. *ast.File {
+. }
+}
A pkg/typecheck/testdata/scopes/fn.snow => pkg/typecheck/testdata/scopes/fn.snow +2 -0
@@ 0,0 1,2 @@
+fn test() {
+}
A pkg/typecheck/testdata/scopes/fn.snow.err => pkg/typecheck/testdata/scopes/fn.snow.err +0 -0
A pkg/typecheck/testdata/scopes/fn.snow.want => pkg/typecheck/testdata/scopes/fn.snow.want +7 -0
@@ 0,0 1,7 @@
+<nil> {
+. *ast.File {
+. . test
+. . *ast.FnDef {
+. . }
+. }
+}
A pkg/typecheck/testdata/scopes/fn_nested_block.snow => pkg/typecheck/testdata/scopes/fn_nested_block.snow +11 -0
@@ 0,0 1,11 @@
+fn test(x: int, y: int) -> int {
+ let s1: int
+ {
+ let s2: int
+ {
+ let inside: int
+ var s1: int
+ }
+ let s2b: int
+ }
+}
A pkg/typecheck/testdata/scopes/fn_nested_block.snow.err => pkg/typecheck/testdata/scopes/fn_nested_block.snow.err +0 -0
A pkg/typecheck/testdata/scopes/fn_nested_block.snow.want => pkg/typecheck/testdata/scopes/fn_nested_block.snow.want +18 -0
@@ 0,0 1,18 @@
+<nil> {
+. *ast.File {
+. . test
+. . *ast.FnDef {
+. . . s1
+. . . x
+. . . y
+. . . *ast.Block {
+. . . . s2
+. . . . s2b
+. . . . *ast.Block {
+. . . . . inside
+. . . . . s1
+. . . . }
+. . . }
+. . }
+. }
+}
A pkg/typecheck/testdata/scopes/fn_params.snow => pkg/typecheck/testdata/scopes/fn_params.snow +3 -0
@@ 0,0 1,3 @@
+fn test(x: int, y: int) -> int {
+ return x + y
+}
A pkg/typecheck/testdata/scopes/fn_params.snow.err => pkg/typecheck/testdata/scopes/fn_params.snow.err +0 -0
A pkg/typecheck/testdata/scopes/fn_params.snow.want => pkg/typecheck/testdata/scopes/fn_params.snow.want +9 -0
@@ 0,0 1,9 @@
+<nil> {
+. *ast.File {
+. . test
+. . *ast.FnDef {
+. . . x
+. . . y
+. . }
+. }
+}
A pkg/typecheck/testdata/scopes/fn_params_locals.snow => pkg/typecheck/testdata/scopes/fn_params_locals.snow +4 -0
@@ 0,0 1,4 @@
+fn test(x: int, y: int) -> int {
+ var z = x + y
+ return z
+}
A pkg/typecheck/testdata/scopes/fn_params_locals.snow.err => pkg/typecheck/testdata/scopes/fn_params_locals.snow.err +0 -0
A pkg/typecheck/testdata/scopes/fn_params_locals.snow.want => pkg/typecheck/testdata/scopes/fn_params_locals.snow.want +10 -0
@@ 0,0 1,10 @@
+<nil> {
+. *ast.File {
+. . test
+. . *ast.FnDef {
+. . . x
+. . . y
+. . . z
+. . }
+. }
+}
A pkg/typecheck/testdata/scopes/fns.snow => pkg/typecheck/testdata/scopes/fns.snow +12 -0
@@ 0,0 1,12 @@
+fn add(x: int, y: int) -> int {
+ let z = x + y
+ return z
+}
+
+fn main() {
+ let result: int = add(1, 3)
+ println(result)
+}
+
+@extern{import: "fmt", symbol: "Println"}
+fn println(x: int)
A pkg/typecheck/testdata/scopes/fns.snow.err => pkg/typecheck/testdata/scopes/fns.snow.err +0 -0
A pkg/typecheck/testdata/scopes/fns.snow.want => pkg/typecheck/testdata/scopes/fns.snow.want +18 -0
@@ 0,0 1,18 @@
+<nil> {
+. *ast.File {
+. . add
+. . main
+. . println
+. . *ast.FnDef {
+. . . x
+. . . y
+. . . z
+. . }
+. . *ast.FnDef {
+. . . result
+. . }
+. . *ast.FnDef {
+. . . x
+. . }
+. }
+}
A pkg/typecheck/testdata/scopes/let.snow => pkg/typecheck/testdata/scopes/let.snow +1 -0
@@ 0,0 1,1 @@
+let y: string = "abc"
A pkg/typecheck/testdata/scopes/let.snow.err => pkg/typecheck/testdata/scopes/let.snow.err +0 -0
A pkg/typecheck/testdata/scopes/let.snow.want => pkg/typecheck/testdata/scopes/let.snow.want +5 -0
@@ 0,0 1,5 @@
+<nil> {
+. *ast.File {
+. . y
+. }
+}
A pkg/typecheck/testdata/scopes/var.snow => pkg/typecheck/testdata/scopes/var.snow +1 -0
@@ 0,0 1,1 @@
+var x: int
A pkg/typecheck/testdata/scopes/var.snow.err => pkg/typecheck/testdata/scopes/var.snow.err +0 -0
A pkg/typecheck/testdata/scopes/var.snow.want => pkg/typecheck/testdata/scopes/var.snow.want +5 -0
@@ 0,0 1,5 @@
+<nil> {
+. *ast.File {
+. . x
+. }
+}
M pkg/typecheck/typecheck.go => pkg/typecheck/typecheck.go +23 -1
@@ 8,10 8,31 @@ import (
"strings"
"git.sr.ht/~mna/snow/pkg/ast"
+ "git.sr.ht/~mna/snow/pkg/parser"
"git.sr.ht/~mna/snow/pkg/scanner"
"git.sr.ht/~mna/snow/pkg/token"
)
+// CheckFiles is a helper function that parses and typechecks code for the
+// provided source files. It returns the typechecked *Unit and an error.
+// The error, if non-nil, is guaranteed to be a scanner.ErrorList.
+func CheckFiles(files ...string) (*Unit, error) {
+ if len(files) == 0 {
+ return nil, nil
+ }
+
+ fset, fs, err := parser.ParseFiles(files...)
+ if err != nil {
+ return nil, err
+ }
+
+ var c checker
+ for _, f := range fs {
+ c.checkFile(fset, f)
+ }
+ return c.unit, c.errors.Err()
+}
+
type checker struct {
fset *token.FileSet
unit *Unit
@@ 64,6 85,7 @@ func (c *checker) Visit(n ast.Node) ast.Visitor {
// no need to close this scope, currentScope is reset to Universe
// on each new file to check.
c.openScope(n)
+ return c
case *ast.FnDef:
// the function's name is in the current scope, while the arguments are in
@@ 164,7 186,7 @@ func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) {
const ind = ". "
indn := strings.Repeat(ind, n)
- fmt.Fprintf(w, "%s%s scope %p {\n", indn, s.Node, s)
+ fmt.Fprintf(w, "%s%T {\n", indn, s.Node)
syms := make([]string, 0, len(s.Symbols))
for sym := range s.Symbols {
M pkg/typecheck/typecheck_test.go => pkg/typecheck/typecheck_test.go +80 -0
@@ 1,1 1,81 @@
package typecheck
+
+import (
+ "bytes"
+ "flag"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "git.sr.ht/~mna/snow/pkg/scanner"
+ "github.com/kylelemons/godebug/diff"
+)
+
+var testUpdateScopesTests = flag.Bool("test.update-scopes-tests", false, "If set, replace expected typecheck scopes test results with actual results.")
+
+func TestScopes(t *testing.T) {
+ baseDir := filepath.Join("testdata", "scopes")
+ fis, err := ioutil.ReadDir(baseDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, fi := range fis {
+ if !fi.Mode().IsRegular() {
+ continue
+ }
+ if filepath.Ext(fi.Name()) != ".snow" {
+ continue
+ }
+
+ t.Run(fi.Name(), func(t *testing.T) {
+ unit, err := CheckFiles(filepath.Join(baseDir, fi.Name()))
+ if unit == nil && err != nil {
+ t.Fatal(err)
+ }
+
+ var ebuf bytes.Buffer
+ scanner.PrintError(&ebuf, err)
+
+ var buf bytes.Buffer
+ //pretty.Println(unit)
+ unit.Universe.WriteTo(&buf, 0, true)
+
+ wantFile := filepath.Join(baseDir, fi.Name()+".want")
+ errFile := filepath.Join(baseDir, fi.Name()+".err")
+ if *testUpdateScopesTests {
+ if err := ioutil.WriteFile(wantFile, buf.Bytes(), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile(errFile, ebuf.Bytes(), 0666); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }
+
+ // validate scopes output
+ want, err := ioutil.ReadFile(wantFile)
+ if err != nil && !os.IsNotExist(err) {
+ t.Fatal(err)
+ }
+ if patch := diff.Diff(buf.String(), string(want)); patch != "" {
+ t.Logf("got:\n%s\n", buf.String())
+ t.Logf("want:\n%s\n", string(want))
+ t.Errorf("diff:\n%s\n", patch)
+ }
+
+ // validate errors output
+ ewant, err := ioutil.ReadFile(errFile)
+ if err != nil && !os.IsNotExist(err) {
+ t.Fatal(err)
+ }
+ if patch := diff.Diff(ebuf.String(), string(ewant)); patch != "" {
+ t.Logf("got error(s):\n%s\n", ebuf.String())
+ t.Logf("want error(s):\n%s\n", string(ewant))
+ t.Errorf("diff error(s):\n%s\n", patch)
+ }
+ })
+ }
+}