tc_checker.mx raw
1 package main
2
3 import "fmt"
4
5 // Importer resolves import paths to Packages.
6 type Importer interface {
7 Import(path string) (*TCPackage, error)
8 }
9
10 // Config controls type checker behavior.
11 type Config struct {
12 Importer Importer
13 Error func(err error) // if nil, first error is returned and checking stops
14 }
15
16 // Checker is the Moxie type checker.
17 // Create one with NewChecker, then call Files to type-check a package.
18 type Checker struct {
19 conf *Config
20 pkg *TCPackage
21 info *Info
22 files []*File
23 iota int32 // current iota value
24 errors []error
25 localScope *Scope // innermost scope during function body checking; nil at pkg level
26 }
27
28 // NewChecker creates a checker for pkg. info receives the type information.
29 func NewChecker(conf *Config, pkg *TCPackage, info *Info) *Checker {
30 return &Checker{conf: conf, pkg: pkg, info: info}
31 }
32
33 // Check type-checks the given files as package pkg.
34 // It returns an error if type checking fails.
35 func Check(path string, files []*File, importer Importer) (*TCPackage, *Info, error) {
36 pkg := NewTCPackage(path, packageName(files))
37 info := newInfo()
38 conf := &Config{Importer: importer}
39 c := NewChecker(conf, pkg, info)
40 if err := c.Files(files); err != nil {
41 return pkg, info, err
42 }
43 return pkg, info, nil
44 }
45
46 // packageName returns the package name declared in the first file, or "main".
47 func packageName(files []*File) string {
48 for _, f := range files {
49 if f.PkgName != nil {
50 return f.PkgName.Value
51 }
52 }
53 return "main"
54 }
55
56 // Files type-checks the given files as the checker's package.
57 func (c *Checker) Files(files []*File) error {
58 c.files = files
59
60 for _, f := range files {
61 c.collectDecls(f)
62 }
63
64 for _, f := range files {
65 for _, decl := range f.DeclList {
66 if d, ok := decl.(*TypeDecl); ok {
67 c.checkTypeDecl(d)
68 }
69 }
70 }
71
72 for _, f := range files {
73 for _, decl := range f.DeclList {
74 switch decl.(type) {
75 case *TypeDecl:
76 default:
77 c.checkDecl(decl)
78 }
79 }
80 }
81
82 for _, f := range files {
83 for _, decl := range f.DeclList {
84 if fd, ok := decl.(*FuncDecl); ok {
85 c.checkFuncBody(fd)
86 }
87 }
88 }
89
90 if len(c.errors) > 0 {
91 return c.errors[0]
92 }
93 return nil
94 }
95
96 // error records a type error at pos.
97 func (c *Checker) errorf(pos Pos, format string, args ...interface{}) {
98 err := &TypeError{Pos: pos, Msg: fmt.Sprintf(format, args...)}
99 if c.conf.Error != nil {
100 c.conf.Error(err)
101 c.errors = append(c.errors, err)
102 } else {
103 c.errors = append(c.errors, err)
104 }
105 }
106
107 // TypeError is a type-checking error with position info.
108 type TypeError struct {
109 Pos Pos
110 Msg string
111 }
112
113 func (e *TypeError) Error() string {
114 return fmt.Sprintf("%s: %s", e.Pos, e.Msg)
115 }
116
117 // record stores type info for an expression.
118 func (c *Checker) record(e Expr, tv TCTypeAndValue) {
119 if c.info != nil {
120 c.info.Types[e] = tv
121 }
122 stv := TypeAndValue{Type: tv.Type}
123 stv.Value = tv.Value
124 e.SetTypeInfo(stv)
125 }
126
127 // openScope creates a child scope of s and records it for node n.
128 func (c *Checker) openScope(n Node, parent *Scope) *Scope {
129 s := NewScope(parent)
130 if c.info != nil {
131 c.info.Scopes[n] = s
132 }
133 return s
134 }
135
136 // lookup finds a named object starting from s, then universe.
137 func (c *Checker) lookup(name string, s *Scope) (*Scope, Object) {
138 if found, obj := s.LookupParent(name); obj != nil {
139 return found, obj
140 }
141 if obj := Universe.Lookup(name); obj != nil {
142 return Universe, obj
143 }
144 return nil, nil
145 }
146
147 // lookupType finds a type name in localScope (if set), then package scope, then universe.
148 // Used by resolveTypeName to find locally-defined types (e.g. inside function bodies).
149 func (c *Checker) lookupType(name string) (*Scope, Object) {
150 if c.localScope != nil {
151 if sc, obj := c.localScope.LookupParent(name); obj != nil {
152 return sc, obj
153 }
154 }
155 return c.lookup(name, c.pkg.scope)
156 }
157