tc_checker.mx raw
1 package types
2
3 import (
4 "git.smesh.lol/moxie/pkg/syntax"
5 "git.smesh.lol/moxie/pkg/token"
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 []*syntax.File
26 iota int64 // current iota value
27 errors []error
28 localScope *Scope // innermost scope during function body checking; nil at pkg level
29 fileScope *Scope // scope of the file currently being checked; holds its imports
30 fileScopes map[*syntax.File]*Scope
31 }
32
33 // NewChecker creates a checker for pkg. info receives the type information.
34 func NewChecker(conf *Config, pkg *TCPackage, info *Info) (c *Checker) {
35 return &Checker{conf: conf, pkg: pkg, info: info}
36 }
37
38 // Check type-checks the given files as package pkg.
39 // It returns an error if type checking fails.
40 func Check(path string, files []*syntax.File, importer Importer) (pkg *TCPackage, info *Info, err error) {
41 pkg = NewTCPackage(path, packageName(files))
42 info = newInfo()
43 conf := &Config{Importer: importer}
44 c := NewChecker(conf, pkg, info)
45 if err = c.Files(files); err != nil {
46 return pkg, info, err
47 }
48 return pkg, info, nil
49 }
50
51 // packageName returns the package name declared in the first file, or "main".
52 func packageName(files []*syntax.File) (s string) {
53 for _, f := range files {
54 if f.PkgName != nil {
55 return f.PkgName.Value
56 }
57 }
58 return "main"
59 }
60
61 // Files type-checks the given files as the checker's package.
62 func (c *Checker) Files(files []*syntax.File) (err error) {
63 c.files = files
64
65 // Imports are file-scoped: each file gets its own scope (parent =
66 // pkg.scope) holding the PkgNames it imports. Two files may bind the
67 // same local name to different packages.
68 c.fileScopes = map[*syntax.File]*Scope{}
69 for _, f := range files {
70 c.fileScopes[f] = NewScope(c.pkg.Scope)
71 if c.info != nil && c.info.Scopes != nil {
72 c.info.Scopes[f] = c.fileScopes[f]
73 }
74 }
75
76 for _, f := range files {
77 c.fileScope = c.fileScopes[f]
78 c.collectDecls(f)
79 }
80
81 // Count methods per receiver type name, then pre-allocate
82 // Methods slices with exact capacity. Two-pass: count before cut.
83 c.preallocMethods(files)
84
85 for _, f := range files {
86 c.fileScope = c.fileScopes[f]
87 for _, decl := range f.DeclList {
88 if d, ok := decl.(*syntax.TypeDecl); ok {
89 c.checkTypeDecl(d)
90 }
91 }
92 }
93
94 for _, f := range files {
95 c.fileScope = c.fileScopes[f]
96 for _, decl := range f.DeclList {
97 switch decl.(type) {
98 case *syntax.TypeDecl:
99 default:
100 c.checkDecl(decl)
101 }
102 }
103 }
104
105 for _, f := range files {
106 c.fileScope = c.fileScopes[f]
107 for _, decl := range f.DeclList {
108 if fd, ok := decl.(*syntax.FuncDecl); ok {
109 c.checkFuncBody(fd)
110 }
111 }
112 }
113 c.fileScope = nil
114
115 if len(c.errors) > 0 {
116 return c.errors[0]
117 }
118 return nil
119 }
120
121 // error records a type error at pos.
122 func (c *Checker) errorf(pos token.Pos, msg string) {
123 err := &TypeError{Pos: pos, Msg: msg}
124 if c.conf.Error != nil {
125 c.conf.Error(err)
126 }
127 push(c.errors, err)
128 }
129
130 // TypeError is a type-checking error with position info.
131 type TypeError struct {
132 Pos token.Pos
133 Msg string
134 }
135
136 func (e *TypeError) Error() (s string) {
137 return e.Pos.String() | ": " | e.Msg
138 }
139
140 func (e *TypeError) String() (s string) {
141 return e.Error()
142 }
143
144 // recvTypeName extracts the receiver type name from a FuncDecl's
145 // receiver field without resolving types. Returns "" if not extractable.
146 func recvTypeName(d *syntax.FuncDecl) (name string) {
147 if d.Recv == nil {
148 return ""
149 }
150 t := d.Recv.Type
151 // Unwrap pointer: *T -> T
152 if op, ok := t.(*syntax.Operation); ok && op.Y == nil {
153 t = op.X
154 }
155 // Unwrap index: T[X] -> T (generic receiver)
156 if idx, ok := t.(*syntax.IndexExpr); ok {
157 t = idx.X
158 }
159 if n, ok := t.(*syntax.Name); ok {
160 return n.Value
161 }
162 return ""
163 }
164
165 // preallocMethods counts methods per receiver type name across all files,
166 // then pre-allocates the Methods slice on each type with exact capacity.
167 // Two-pass: count before cut. push() fills what was already measured.
168 func (c *Checker) preallocMethods(files []*syntax.File) {
169 // Pass 1: count methods per receiver type name.
170 counts := map[string]int32{}
171 for _, f := range files {
172 for _, decl := range f.DeclList {
173 if fd, ok := decl.(*syntax.FuncDecl); ok && fd.Recv != nil {
174 name := recvTypeName(fd)
175 if name != "" {
176 counts[name] = counts[name] + 1
177 }
178 }
179 }
180 }
181 // Pass 2: pre-allocate Methods slices on declared types.
182 for name, count := range counts {
183 obj := c.pkg.Scope.Lookup(name)
184 if obj == nil {
185 continue
186 }
187 tn, ok := obj.(*TypeName)
188 if !ok {
189 continue
190 }
191 if tn.Typ == nil {
192 continue
193 }
194 switch t := tn.Typ.(type) {
195 case *Named:
196 if t.Methods == nil {
197 t.Methods = []*TCFunc{:0:count}
198 }
199 case *Basic:
200 if t.Methods == nil {
201 t.Methods = []*TCFunc{:0:count}
202 }
203 }
204 }
205 }
206
207 // record stores type info for an expression.
208 func (c *Checker) record(e syntax.Expr, tv TCTypeAndValue) {
209 if c.info != nil {
210 c.info.Types[e] = tv
211 }
212 var styp syntax.Type
213 if tv.Type != nil {
214 if st, ok := tv.Type.(syntax.Type); ok {
215 styp = st
216 }
217 }
218 stv := syntax.TypeAndValue{Type: styp}
219 stv.Value = tv.Value
220 e.SetTypeInfo(stv)
221 }
222
223 // openScope creates a child scope of s and records it for node n.
224 func (c *Checker) openScope(n syntax.Node, parent *Scope) (s *Scope) {
225 s = NewScope(parent)
226 if c.info != nil {
227 c.info.Scopes[n] = s
228 }
229 return s
230 }
231
232 // lookup finds a named object starting from s, then universe.
233 func (c *Checker) lookup(name string, s *Scope) (sc *Scope, obj Object) {
234 sc, obj = s.LookupParent(name)
235 if obj != nil {
236 return sc, obj
237 }
238 obj = Universe.Lookup(name)
239 if obj != nil {
240 return Universe, obj
241 }
242 return nil, nil
243 }
244
245 // lookupType finds a type name in localScope (if set), then the current
246 // file scope (imports), then package scope, then universe.
247 // Used by resolveTypeName to find locally-defined types (e.g. inside function bodies).
248 func (c *Checker) lookupType(name string) (sc *Scope, obj Object) {
249 if c.localScope != nil {
250 sc, obj = c.localScope.LookupParent(name)
251 if obj != nil {
252 return sc, obj
253 }
254 }
255 if c.fileScope != nil {
256 sc, obj = c.fileScope.LookupParent(name)
257 if obj != nil {
258 return sc, obj
259 }
260 }
261 return c.lookup(name, c.pkg.Scope)
262 }
263