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