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         int64  // 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