~mna/snow

43b7e7d27d4ff5e77d70225899689c4444bc4844 — Martin Angers 1 year, 10 months ago 7084b95
pkg/semantic: more sanity checks
M pkg/semantic/runner.go => pkg/semantic/runner.go +6 -0
@@ 10,6 10,7 @@ import (
const (
	TranslatePass = iota
	TypeAssignPass
	TypeCheckPass
)

// Run parses and typechecks code for the provided source files. It returns the


@@ 42,6 43,11 @@ func Run(maxPass int, files ...string) (*Unit, error) {
		typeassign(unit, errh)
	}

	if maxPass >= TypeCheckPass {
		// step 3: type-check the program
		typecheck(unit, errh)
	}

	return unit, removeDuplicateErrs(errs).Err()
}


M pkg/semantic/semantic.go => pkg/semantic/semantic.go +20 -0
@@ 227,6 227,26 @@ type Call struct {
	commonExpr
}

// Values returns the argument values of a Call keyed by their label.
// It should only be called for attributes (Call with IsAttr==true),
// as those have compile-time values as arguments and require labels.
func (c *Call) Values() map[string]interface{} {
	vals := make(map[string]interface{}, len(c.Args))
	for i, arg := range c.Args {
		lbl := c.Labels[i]

		var val interface{}
		switch arg := arg.(type) {
		case *LitInt:
			val = arg.Value
		case *LitString:
			val = arg.Value
		}
		vals[lbl] = val
	}
	return vals
}

type Selector struct {
	Left Expr
	Sel  *Ident

M pkg/semantic/translate_pass_test.go => pkg/semantic/translate_pass_test.go +20 -14
@@ 1,13 1,15 @@
package semantic
package semantic_test

import (
	"bytes"
	"flag"
	"fmt"
	"path/filepath"
	"testing"

	"git.sr.ht/~mna/snow/pkg/internal/filetest"
	"git.sr.ht/~mna/snow/pkg/scanner"
	"git.sr.ht/~mna/snow/pkg/semantic"
)

var testUpdateScopesTests = flag.Bool("test.update-scopes-tests", false, "If set, replace expected semantic scopes test results with actual results.")


@@ 18,7 20,7 @@ func TestScopes(t *testing.T) {

	for _, fi := range filetest.SourceFiles(t, baseDir) {
		t.Run(fi.Name(), func(t *testing.T) {
			unit, err := Run(TranslatePass, filepath.Join(baseDir, fi.Name()))
			unit, err := semantic.Run(semantic.TranslatePass, filepath.Join(baseDir, fi.Name()))

			var ebuf bytes.Buffer
			scanner.PrintError(&ebuf, err)


@@ 35,30 37,34 @@ func TestScopes(t *testing.T) {

			// sanity check: all nodes have a scope
			var hasScope visitorFunc
			hasScope = func(n Node) Visitor {
			hasScope = func(n semantic.Node) semantic.Visitor {
				if n == nil {
					return nil
				}
				if n.Scope() == nil {
					lpos := unit.FileSet.Position(n.Pos())
					switch n := n.(type) {
					case Decl:
						t.Fatalf("%s: no scope for declaration %s", lpos, n.Ident())
					case *Ident:
						t.Fatalf("%s: no scope for identifier %s", lpos, n.Name)
					default:
						t.Fatalf("%s: no scope for node %T", lpos, n)
					}
					t.Fatalf("%s: no scope for %s", lpos, bestLabelFor(n))
				}
				return hasScope
			}
			Walk(hasScope, unit)
			semantic.Walk(hasScope, unit)
		})
	}
}

type visitorFunc func(Node) Visitor
type visitorFunc func(semantic.Node) semantic.Visitor

func (v visitorFunc) Visit(n Node) Visitor {
func (v visitorFunc) Visit(n semantic.Node) semantic.Visitor {
	return v(n)
}

func bestLabelFor(n semantic.Node) string {
	switch n := n.(type) {
	case semantic.Decl:
		return n.Ident()
	case *semantic.Ident:
		return n.Name
	default:
		return fmt.Sprintf("%T", n)
	}
}

M pkg/semantic/typeassign_pass_test.go => pkg/semantic/typeassign_pass_test.go +26 -0
@@ 36,6 36,32 @@ func TestTypes(t *testing.T) {

			filetest.DiffOutput(t, fi, buf.String(), expectDir, testUpdateTypesTests)
			filetest.DiffErrors(t, fi, ebuf.String(), expectDir, testUpdateTypesTests)

			// sanity check invariants:
			// - all expressions have a type
			// - all declarations have a type
			// - all valid identifiers have a Ref
			var check visitorFunc
			check = func(n semantic.Node) semantic.Visitor {
				if n == nil {
					return nil
				}
				switch n := n.(type) {
				case semantic.Typed:
					if n.Type() == nil {
						lpos := unit.FileSet.Position(n.Pos())
						t.Fatalf("%s: no type for %s", lpos, bestLabelFor(n))
					}
					if id, ok := n.(*semantic.Ident); ok && id.Type().Valid() && id.Index < 0 {
						if id.Ref == nil {
							lpos := unit.FileSet.Position(n.Pos())
							t.Fatalf("%s: no Ref for identifier %s", lpos, id.Name)
						}
					}
				}
				return check
			}
			semantic.Walk(check, unit)
		})
	}
}

M pkg/semantic/typecheck_pass.go => pkg/semantic/typecheck_pass.go +12 -8
@@ 2,6 2,7 @@ package semantic

import (
	"fmt"
	"path/filepath"
	"reflect"
	"sort"
	"strings"


@@ 128,7 129,6 @@ func (t *typecheckVisitor) Visit(n Node) Visitor {
			}
		}
		if n.Value != nil {
			// TODO: if in a struct scope, cannot access outer expressions nor call struct methods
			Walk(t, n.Value)
			var T Type
			if !t.expectTypeCtx(n.Value, &T, TypeContextValues...) {


@@ 357,6 357,7 @@ func (t *typecheckVisitor) Visit(n Node) Visitor {
			return nil
		}
		/*
			// TODO: if in a struct scope, cannot access outer expressions nor call struct methods
			TODO:
				if c.fnInfo.methodMinDepth > 0 {
					// inside a method; if this is a use of a variable, make sure it is in a


@@ 427,15 428,18 @@ func (t *typecheckVisitor) typecheckFnAttrs(fn *Fn) {
		if fn.IsRef && fn.MethodOf != nil {
			t.errh(fn.Pos(), fmt.Sprintf("@%s function %s cannot have a ref modifier", ExternAttrName, fn.Ident()))
		}
	}

	/*
		if es := fno.ExternalSymbol; es != nil {
			if _, obj := fno.ParentScope().LookupChain(es.Pkg, n.End()); obj != nil {
				c.errHandler(attrPos, fmt.Sprintf("external package identifier %s already declared", es.Pkg))
		vals := extern.Values()
		if len(vals) > 0 {
			pkg := vals["pkg"].(string)
			if pkg == "" {
				imp := vals["import"].(string)
				pkg = filepath.Base(imp)
			}
			if decl := fn.Scope().LookupChain(pkg, fn.Pos()); decl != nil {
				t.errh(extern.Pos(), fmt.Sprintf("external package identifier %s already declared", pkg))
			}
		}
	*/
	}
}

func (t *typecheckVisitor) typecheckMainFn(fn *Fn, st *SignatureType) {

A pkg/semantic/typecheck_pass_test.go => pkg/semantic/typecheck_pass_test.go +1 -0
@@ 0,0 1,1 @@
package semantic_test