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