~mna/snow unlisted

7a52a20e513a370b0833640bafc3e723cc445932 — Martin Angers 1 year, 1 month ago 12b9e4a
pkg/typecheck: test scopes
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)
			}
		})
	}
}