package typecheck import "moxie/syntax" // checkBlock type-checks a block statement. It always opens a fresh child // scope so that local declarations inside the block don't leak into the // parent scope and don't conflict with sibling declarations (e.g. var ok // in an if-body vs ok from the if-init). func (c *Checker) checkBlock(b *syntax.BlockStmt, parent *Scope) { if b == nil { return } scope := c.openScope(b, parent) saved := c.localScope c.localScope = scope defer func() { c.localScope = saved }() for _, s := range b.List { c.checkStmt(s, scope) } } func (c *Checker) checkStmt(s syntax.Stmt, scope *Scope) { if s == nil { return } switch s := s.(type) { case *syntax.EmptyStmt: // nothing case *syntax.ExprStmt: c.checkExpr(s.X, scope) case *syntax.SendStmt: c.checkExpr(s.Chan, scope) c.checkExpr(s.Value, scope) case *syntax.AssignStmt: c.checkAssign(s, scope) case *syntax.BlockStmt: inner := c.openScope(s, scope) c.checkBlock(s, inner) case *syntax.DeclStmt: for _, d := range s.DeclList { c.checkLocalDecl(d, scope) } case *syntax.IfStmt: inner := c.openScope(s, scope) if s.Init != nil { c.checkStmt(s.Init, inner) } c.checkExpr(s.Cond, inner) c.checkBlock(s.Then, inner) if s.Else != nil { c.checkStmt(s.Else, inner) } case *syntax.ForStmt: inner := c.openScope(s, scope) if rc, ok := s.Init.(*syntax.RangeClause); ok { c.checkRange(rc, inner) } else { if s.Init != nil { c.checkStmt(s.Init, inner) } } if s.Cond != nil { c.checkExpr(s.Cond, inner) } if s.Post != nil { c.checkStmt(s.Post, inner) } c.checkBlock(s.Body, inner) case *syntax.SwitchStmt: c.checkSwitch(s, scope) case *syntax.SelectStmt: c.checkSelect(s, scope) case *syntax.ReturnStmt: if s.Results != nil { c.checkExpr(s.Results, scope) } case *syntax.BranchStmt: // break/continue/goto/fallthrough — label checking deferred case *syntax.LabeledStmt: c.checkStmt(s.Stmt, scope) case *syntax.CallStmt: // go/defer c.checkExpr(s.Call, scope) } } func (c *Checker) checkAssign(s *syntax.AssignStmt, scope *Scope) { if s.Rhs == nil { // x++ or x-- c.checkExpr(s.Lhs, scope) return } if s.Op == syntax.Def { // short variable declaration: x, y := ... c.checkShortVarDecl(s, scope) return } c.checkExpr(s.Lhs, scope) c.checkExpr(s.Rhs, scope) } func (c *Checker) checkShortVarDecl(s *syntax.AssignStmt, scope *Scope) { // Evaluate rhs first. rhsType := c.checkExpr(s.Rhs, scope) lhsNames := exprNames(s.Lhs) // Determine which names are new in the current scope (not parent scopes). // Go spec: at least one name must be new; existing names are re-assigned. newCount := 0 for _, name := range lhsNames { if name.Value != "_" && scope.Lookup(name.Value) == nil { newCount++ } } if newCount == 0 && len(lhsNames) > 0 { // All names already declared in this scope: error. c.errorf(s.Pos(), "no new variables on left side of :=") return } // Assign types. Single-value: use rhsType directly. // Multi-value: we don't yet unpack tuples, so leave type nil for existing names. for i, name := range lhsNames { if name.Value == "_" { continue } if scope.Lookup(name.Value) != nil { // Already in scope: re-assignment, not redeclaration. No action needed. continue } var typ Type if i == 0 && rhsType != nil { typ = rhsType } obj := NewVar(c.pkg, name.Value, typ) c.declare(scope, name, obj) } } func (c *Checker) checkLocalDecl(d syntax.Decl, scope *Scope) { switch d := d.(type) { case *syntax.VarDecl: var typ Type if d.Type != nil { typ = c.resolveTypeExpr(d.Type) } if d.Values != nil && typ == nil { typ = c.checkExpr(d.Values, scope) } for _, name := range d.NameList { obj := NewVar(c.pkg, name.Value, typ) c.declare(scope, name, obj) } case *syntax.TypeDecl: underlying := c.resolveTypeExpr(d.Type) obj := NewTypeName(c.pkg, d.Name.Value, nil) named := NewNamed(obj, underlying) obj.typ = named c.declare(scope, d.Name, obj) case *syntax.ConstDecl: // TODO: constant evaluation for _, name := range d.NameList { obj := NewConst(c.pkg, name.Value, nil, nil) c.declare(scope, name, obj) } } } func (c *Checker) checkRange(rc *syntax.RangeClause, scope *Scope) { rangeType := c.checkExpr(rc.X, scope) if rc.Lhs != nil && rc.Def { names := exprNames(rc.Lhs) keyType, valType := rangeKeyVal(rangeType) types := []Type{keyType, valType} for i, name := range names { if name.Value == "_" { continue } var t Type if i < len(types) { t = types[i] } obj := NewVar(c.pkg, name.Value, t) c.declare(scope, name, obj) } } } func (c *Checker) checkSwitch(s *syntax.SwitchStmt, scope *Scope) { inner := c.openScope(s, scope) if s.Init != nil { c.checkStmt(s.Init, inner) } // Type switch: switch v := x.(type) { case T: ... } if s.Tag != nil { if tsg, ok := s.Tag.(*syntax.TypeSwitchGuard); ok { xTyp := c.checkExpr(tsg.X, inner) for _, clause := range s.Body { clauseScope := c.openScope(clause, inner) // Bind the guard variable to the case type in each clause. if tsg.Lhs != nil { caseTyp := xTyp // default / multi-type: keep interface type if clause.Cases != nil { if _, multi := clause.Cases.(*syntax.ListExpr); !multi { // nil case or single type: nil keeps interface type. if n, isNil := clause.Cases.(*syntax.Name); !isNil || n.Value != "nil" { if t := c.resolveTypeExpr(clause.Cases); t != nil { caseTyp = t } } } } obj := NewVar(c.pkg, tsg.Lhs.Value, caseTyp) clauseScope.Insert(obj) if c.info != nil { c.info.Implicits[clause] = obj } } for _, stmt := range clause.Body { c.checkStmt(stmt, clauseScope) } } return } } // Expression switch. if s.Tag != nil { c.checkExpr(s.Tag, inner) } for _, clause := range s.Body { clauseScope := c.openScope(clause, inner) if clause.Cases != nil { c.checkExpr(clause.Cases, clauseScope) } for _, stmt := range clause.Body { c.checkStmt(stmt, clauseScope) } } } func (c *Checker) checkSelect(s *syntax.SelectStmt, scope *Scope) { for _, clause := range s.Body { clauseScope := c.openScope(clause, scope) if clause.Comm != nil { c.checkStmt(clause.Comm, clauseScope) } for _, stmt := range clause.Body { c.checkStmt(stmt, clauseScope) } } } // exprNames extracts *syntax.Name nodes from an expression. // Used for the LHS of := and range clauses. func exprNames(e syntax.Expr) []*syntax.Name { if e == nil { return nil } if n, ok := e.(*syntax.Name); ok { return []*syntax.Name{n} } if l, ok := e.(*syntax.ListExpr); ok { var names []*syntax.Name for _, el := range l.ElemList { if n, ok := el.(*syntax.Name); ok { names = append(names, n) } } return names } return nil } // rangeKeyVal returns the key and value types for ranging over t. func rangeKeyVal(t Type) (key, val Type) { if t == nil { return nil, nil } switch t := t.Underlying().(type) { case *Array: return Typ[Int32], t.elem case *Slice: return Typ[Int32], t.elem case *Map: return t.key, t.elem case *Chan: return t.elem, nil case *Basic: if t.info&IsString != 0 { return Typ[Int32], Typ[Uint8] } } return nil, nil }