]> Cypherpunks repositories - gostls13.git/commitdiff
exp/types/staging: support for typechecking (most) builtins
authorRobert Griesemer <gri@golang.org>
Wed, 26 Sep 2012 00:38:43 +0000 (17:38 -0700)
committerRobert Griesemer <gri@golang.org>
Wed, 26 Sep 2012 00:38:43 +0000 (17:38 -0700)
This code relies on some functions that are not yet in staging,
but it get's harder to keep all this in sync in a piece-meal
fashion.

R=rsc
CC=golang-dev
https://golang.org/cl/6492124

src/pkg/exp/types/staging/builtins.go [new file with mode: 0644]
src/pkg/exp/types/staging/testdata/builtins.src [new file with mode: 0644]

diff --git a/src/pkg/exp/types/staging/builtins.go b/src/pkg/exp/types/staging/builtins.go
new file mode 100644 (file)
index 0000000..ef9ae80
--- /dev/null
@@ -0,0 +1,349 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file implements typechecking of builtin function calls.
+
+package types
+
+import (
+       "go/ast"
+       "go/token"
+)
+
+// builtin typechecks a built-in call. The built-in type is bin, and iota is the current
+// value of iota or -1 if iota doesn't have a value in the current context. The result
+// of the call is returned via x. If the call has type errors, the returned x is marked
+// as invalid (x.mode == invalid).
+//
+func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) {
+       args := call.Args
+       id := bin.id
+
+       // declare before goto's
+       var arg0 ast.Expr
+       var typ0 Type
+
+       // check argument count
+       n := len(args)
+       msg := ""
+       if n < bin.nargs {
+               msg = "not enough"
+       } else if !bin.isVariadic && n > bin.nargs {
+               msg = "too many"
+       }
+       if msg != "" {
+               check.invalidOp(call.Pos(), msg+"arguments for %s (expected %d, found %d)", call, bin.nargs, n)
+               goto Error
+       }
+
+       // common case: evaluate first argument if present;
+       // if it is an expression, x has the expression value
+       if n > 0 {
+               arg0 = args[0]
+               switch id {
+               case _Make, _New:
+                       // argument must be a type
+                       typ0 = underlying(check.typ(arg0, false))
+                       if typ0 == Typ[Invalid] {
+                               goto Error
+                       }
+               case _Trace:
+                       // _Trace implementation does the work
+               default:
+                       // argument must be an expression
+                       check.expr(x, arg0, nil, iota)
+                       if x.mode == invalid {
+                               goto Error
+                       }
+                       typ0 = underlying(x.typ)
+               }
+       }
+
+       switch id {
+       case _Append:
+               s, ok := typ0.(*Slice)
+               if !ok {
+                       check.invalidArg(x.pos(), "%s is not a typed slice", x)
+                       goto Error
+               }
+               for _, arg := range args[1:] {
+                       check.expr(x, arg, nil, iota)
+                       if x.mode == invalid {
+                               goto Error
+                       }
+                       // TODO(gri) check assignability
+               }
+               x.mode = value
+               x.typ = s
+
+       case _Cap, _Len:
+               mode := invalid
+               var val interface{}
+               switch typ := implicitDeref(typ0).(type) {
+               case *Basic:
+                       if isString(typ) && id == _Len {
+                               if x.mode == constant {
+                                       mode = constant
+                                       val = int64(len(x.val.(string)))
+                               } else {
+                                       mode = value
+                               }
+                       }
+
+               case *Array:
+                       mode = value
+                       if !containsCallsOrReceives(arg0) {
+                               mode = constant
+                               val = typ.Len
+                       }
+
+               case *Slice, *Chan:
+                       mode = value
+
+               case *Map:
+                       if id == _Len {
+                               mode = value
+                       }
+               }
+
+               if mode == invalid {
+                       check.invalidArg(x.pos(), "%s for %s", x, bin.name)
+                       goto Error
+               }
+               x.mode = mode
+               x.typ = Typ[Int]
+               x.val = val
+
+       case _Close:
+               ch, ok := typ0.(*Chan)
+               if !ok {
+                       check.invalidArg(x.pos(), "%s is not a channel", x)
+                       goto Error
+               }
+               if ch.Dir&ast.SEND == 0 {
+                       check.invalidArg(x.pos(), "%s must not be a receive-only channel", x)
+                       goto Error
+               }
+               x.mode = novalue
+
+       case _Complex:
+               var y operand
+               check.expr(&y, args[1], nil, iota)
+               if y.mode == invalid {
+                       goto Error
+               }
+               // TODO(gri) handle complex(a, b) like (a + toImag(b))
+               unimplemented()
+
+       case _Copy:
+               // TODO(gri) implements checks
+               unimplemented()
+               x.mode = value
+               x.typ = Typ[Int]
+
+       case _Delete:
+               m, ok := typ0.(*Map)
+               if !ok {
+                       check.invalidArg(x.pos(), "%s is not a map", x)
+                       goto Error
+               }
+               check.expr(x, args[1], nil, iota)
+               if x.mode == invalid {
+                       goto Error
+               }
+               if !x.isAssignable(m.Key) {
+                       check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.Key)
+                       goto Error
+               }
+               x.mode = novalue
+
+       case _Imag, _Real:
+               if !isComplex(typ0) {
+                       check.invalidArg(x.pos(), "%s must be a complex number", x)
+                       goto Error
+               }
+               if x.mode == constant {
+                       // nothing to do for x.val == 0
+                       if !isZeroConst(x.val) {
+                               c := x.val.(complex)
+                               if id == _Real {
+                                       x.val = c.re
+                               } else {
+                                       x.val = c.im
+                               }
+                       }
+               } else {
+                       x.mode = value
+               }
+               k := Invalid
+               switch typ0.(*Basic).Kind {
+               case Complex64:
+                       k = Float32
+               case Complex128:
+                       k = Float64
+               case UntypedComplex:
+                       k = UntypedFloat
+               default:
+                       unreachable()
+               }
+               x.typ = Typ[k]
+
+       case _Make:
+               var min int // minimum number of arguments
+               switch typ0.(type) {
+               case *Slice:
+                       min = 2
+               case *Map, *Chan:
+                       min = 1
+               default:
+                       check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0)
+                       goto Error
+               }
+               if n := len(args); n < min || min+1 < n {
+                       check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n)
+                       goto Error
+               }
+               for _, arg := range args[1:] {
+                       check.expr(x, arg, nil, iota)
+                       if !x.isInteger() {
+                               check.invalidArg(x.pos(), "%s must be an integer", x)
+                               // safe to continue
+                       }
+               }
+               x.mode = variable
+               x.typ = typ0
+
+       case _New:
+               x.mode = variable
+               x.typ = &Pointer{Base: typ0}
+
+       case _Panic, _Print, _Println:
+               x.mode = novalue
+
+       case _Recover:
+               x.mode = value
+               x.typ = emptyInterface
+
+       case _Alignof:
+               x.mode = constant
+               x.typ = Typ[Uintptr]
+               // For now we return 1 always as it satisfies the spec's alignment guarantees.
+               // TODO(gri) Extend typechecker API so that platform-specific values can be
+               //           provided.
+               x.val = int64(1)
+
+       case _Offsetof:
+               if _, ok := unparen(x.expr).(*ast.SelectorExpr); !ok {
+                       check.invalidArg(x.pos(), "%s is not a selector", x)
+                       goto Error
+               }
+               x.mode = constant
+               x.typ = Typ[Uintptr]
+               // because of the size guarantees for basic types (> 0 for some),
+               // returning 0 is only correct if two distinct non-zero size
+               // structs can have the same address (the spec permits that)
+               x.val = int64(0)
+
+       case _Sizeof:
+               // basic types with specified sizes have size guarantees; for all others we use 0
+               var size int64
+               if typ, ok := typ0.(*Basic); ok {
+                       size = typ.Size
+               }
+               x.mode = constant
+               x.typ = Typ[Uintptr]
+               x.val = size
+
+       case _Assert:
+               // assert(pred) causes a typechecker error if pred is false.
+               // The result of assert is the value of pred if there is no error.
+               // Note: assert is only available in self-test mode.
+               if x.mode != constant || !isBoolean(typ0) {
+                       check.invalidArg(x.pos(), "%s is not a boolean constant", x)
+                       goto Error
+               }
+               pred, ok := x.val.(bool)
+               if !ok {
+                       check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x)
+                       goto Error
+               }
+               if !pred {
+                       check.errorf(call.Pos(), "%s failed", call)
+                       // compile-time assertion failure - safe to continue
+               }
+
+       case _Trace:
+               // trace(x, y, z, ...) dumps the positions, expressions, and
+               // values of its arguments. The result of trace is the value
+               // of the first argument.
+               // Note: trace is only available in self-test mode.
+               if len(args) == 0 {
+                       check.dump("%s: trace() without arguments", call.Pos())
+                       x.mode = novalue
+                       x.expr = call
+                       return
+               }
+               var t operand
+               x1 := x
+               for _, arg := range args {
+                       check.exprOrType(x1, arg, nil, iota, true) // permit trace for types, e.g.: new(trace(T))
+                       check.dump("%s: %s", x1.pos(), x1)
+                       x1 = &t // use incoming x only for first argument
+               }
+
+       default:
+               check.invalidAST(call.Pos(), "unknown builtin id %d", id)
+               goto Error
+       }
+
+       x.expr = call
+       return
+
+Error:
+       x.mode = invalid
+       x.expr = call
+}
+
+// implicitDeref returns A if typ is of the form *A and A is an array;
+// otherwise it returns typ.
+//
+func implicitDeref(typ Type) Type {
+       if p, ok := typ.(*Pointer); ok {
+               if a, ok := underlying(p.Base).(*Array); ok {
+                       return a
+               }
+       }
+       return typ
+}
+
+// containsCallsOrReceives returns true if the expression x contains
+// function calls or channel receives; it returns false otherwise.
+//
+func containsCallsOrReceives(x ast.Expr) bool {
+       res := false
+       ast.Inspect(x, func(x ast.Node) bool {
+               switch x := x.(type) {
+               case *ast.CallExpr:
+                       res = true
+                       return false
+               case *ast.UnaryExpr:
+                       if x.Op == token.ARROW {
+                               res = true
+                               return false
+                       }
+               }
+               return true
+       })
+       return res
+}
+
+// unparen removes any parentheses surrounding an expression and returns
+// the naked expression.
+//
+func unparen(x ast.Expr) ast.Expr {
+       if p, ok := x.(*ast.ParenExpr); ok {
+               return unparen(p.X)
+       }
+       return x
+}
diff --git a/src/pkg/exp/types/staging/testdata/builtins.src b/src/pkg/exp/types/staging/testdata/builtins.src
new file mode 100644 (file)
index 0000000..c641537
--- /dev/null
@@ -0,0 +1,258 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// builtin calls
+
+package builtins
+
+import "unsafe"
+
+func _append() {
+       var x int
+       var s []byte
+       _0 := append /* ERROR "argument" */ ()
+       _1 := append("foo" /* ERROR "not a typed slice" */)
+       _2 := append(nil /* ERROR "not a typed slice" */, s)
+       _3 := append(x /* ERROR "not a typed slice" */, s)
+       _4 := append(s)
+       append /* ERROR "not used" */ (s)
+}
+
+func _cap() {
+       var a [10]bool
+       var p *[20]int
+       var s []int
+       var c chan string
+       _0 := cap /* ERROR "argument" */ ()
+       _1 := cap /* ERROR "argument" */ (1, 2)
+       _2 := cap(42 /* ERROR "invalid" */)
+       const _3 = cap(a)
+       assert(_3 == 10)
+       const _4 = cap(p)
+       assert(_4 == 20)
+       _5 := cap(c)
+       cap /* ERROR "not used" */ (c)
+}
+
+func _close() {
+       var c chan int
+       var r <-chan int
+       close /* ERROR "argument" */ ()
+       close /* ERROR "argument" */ (1, 2)
+       close(42 /* ERROR "not a channel" */)
+       close(r /* ERROR "receive-only channel" */)
+       close(c)
+}
+
+func _complex() {
+       _0 := complex /* ERROR "argument" */ ()
+       _1 := complex /* ERROR "argument" */ (1)
+       _2 := complex(1, 2)
+       // TODO(gri) add tests checking types
+       complex /* ERROR "not used" */ (1, 2)
+}
+
+func _delete() {
+       var m map[string]int
+       var s string
+       delete /* ERROR "argument" */ ()
+       delete /* ERROR "argument" */ (1)
+       delete /* ERROR "argument" */ (1, 2, 3)
+       delete(m, 0 /* ERROR "not assignable" */)
+       delete(m, s)
+}
+
+func _imag() {
+       var f32 float32
+       var f64 float64
+       var c64 complex64
+       var c128 complex128
+       _0 := imag /* ERROR "argument" */ ()
+       _1 := imag /* ERROR "argument" */ (1, 2)
+       _2 := imag(10 /* ERROR "must be a complex number" */)
+       _3 := imag(2.7182818 /* ERROR "must be a complex number" */)
+       _4 := imag("foo" /* ERROR "must be a complex number" */)
+       const _5 = imag(1 + 2i)
+       assert(_5 == 2)
+       f32 = _5
+       f64 = _5
+       const _6 = imag(0i)
+       assert(_6 == 0)
+       f32 = imag(c64)
+       f64 = imag(c128)
+       f32 = imag /* ERROR "cannot assign" */ (c128)
+       f64 = imag /* ERROR "cannot assign" */ (c64)
+       imag /* ERROR "not used" */ (c64)
+}
+
+func _len() {
+       const c = "foobar"
+       var a [10]bool
+       var p *[20]int
+       var s []int
+       var m map[string]complex128
+       _0 := len /* ERROR "argument" */ ()
+       _1 := len /* ERROR "argument" */ (1, 2)
+       _2 := len(42 /* ERROR "invalid" */)
+       const _3 = len(c)
+       assert(_3 == 6)
+       const _4 = len(a)
+       assert(_4 == 10)
+       const _5 = len(p)
+       assert(_5 == 20)
+       _6 := len(m)
+       len /* ERROR "not used" */ (c)
+
+       // esoteric case
+       var t string
+       var hash map[interface{}][]*[10]int
+       const n = len /* ERROR "not constant" */ (hash[recover()][len(t)])
+       assert /* ERROR "failed" */ (n == 10)
+       var ch <-chan int
+       const nn = len /* ERROR "not constant" */ (hash[<-ch][len(t)])
+       _7 := nn // TODO(gri) remove this once unused constants get type-checked
+}
+
+func _make() {
+       n := 0
+
+       _0 := make /* ERROR "argument" */ ()
+       _1 := make(1 /* ERROR "not a type" */)
+       _2 := make(int /* ERROR "cannot make" */)
+
+       // slices
+       _3 := make/* ERROR "arguments" */ ([]int)
+       _4 := make/* ERROR "arguments" */ ([]int, 2, 3, 4)
+       _5 := make([]int, int /* ERROR "not an expression" */)
+       _6 := make([]int, 10, float32 /* ERROR "not an expression" */)
+       _7 := make([]int, "foo" /* ERROR "must be an integer" */)
+       _8 := make([]int, 10, 2.3 /* ERROR "must be an integer" */)
+       _9 := make([]int, 5, 10.0)
+       _10 := make([]int, 0i)
+       _11 := make([]int, -1, 1<<100) // out-of-range constants lead to run-time errors
+
+       // maps
+       _12 := make /* ERROR "arguments" */ (map[int]string, 10, 20)
+       _13 := make(map[int]float32, int /* ERROR "not an expression" */)
+       _14 := make(map[int]float32, "foo" /* ERROR "must be an integer" */)
+       _15 := make(map[int]float32, 10)
+       _16 := make(map[int]float32, n)
+       _17 := make(map[int]float32, int64(n))
+
+       // channels
+       _22 := make /* ERROR "arguments" */ (chan int, 10, 20)
+       _23 := make(chan int, int /* ERROR "not an expression" */)
+       _24 := make(chan<- int, "foo" /* ERROR "must be an integer" */)
+       _25 := make(<-chan float64, 10)
+       _26 := make(chan chan int, n)
+       _27 := make(chan string, int64(n))
+
+       make /* ERROR "not used" */ ([]int, 10)
+}
+
+func _new() {
+       _0 := new /* ERROR "argument" */ ()
+       _1 := new /* ERROR "argument" */ (1, 2)
+       _3 := new("foo" /* ERROR "not a type" */)
+       _4 := new(float64)
+       _5 := new(struct{ x, y int })
+       _6 := new(*float64)
+       _7 := *_4 == **_6
+       new /* ERROR "not used" */ (int)
+}
+
+func _real() {
+       var f32 float32
+       var f64 float64
+       var c64 complex64
+       var c128 complex128
+       _0 := real /* ERROR "argument" */ ()
+       _1 := real /* ERROR "argument" */ (1, 2)
+       _2 := real(10 /* ERROR "must be a complex number" */)
+       _3 := real(2.7182818 /* ERROR "must be a complex number" */)
+       _4 := real("foo" /* ERROR "must be a complex number" */)
+       const _5 = real(1 + 2i)
+       assert(_5 == 1)
+       f32 = _5
+       f64 = _5
+       const _6 = real(0i)
+       assert(_6 == 0)
+       f32 = real(c64)
+       f64 = real(c128)
+       f32 = real /* ERROR "cannot assign" */ (c128)
+       f64 = real /* ERROR "cannot assign" */ (c64)
+       real /* ERROR "not used" */ (c64)
+}
+
+func _recover() {
+       _0 := recover()
+       _1 := recover /* ERROR "argument" */ (10)
+       recover()
+}
+
+func _Alignof() {
+       var x int
+       _0 := unsafe /* ERROR "argument" */ .Alignof()
+       _1 := unsafe /* ERROR "argument" */ .Alignof(1, 2)
+       _3 := unsafe.Alignof(int /* ERROR "not an expression" */)
+       _4 := unsafe.Alignof(42)
+       _5 := unsafe.Alignof(new(struct{}))
+       unsafe /* ERROR "not used" */ .Alignof(x)
+}
+
+func _Offsetof() {
+       var x struct{ f int }
+       _0 := unsafe /* ERROR "argument" */ .Offsetof()
+       _1 := unsafe /* ERROR "argument" */ .Offsetof(1, 2)
+       _2 := unsafe.Offsetof(int /* ERROR "not an expression" */)
+       _3 := unsafe.Offsetof(x /* ERROR "not a selector" */)
+       _4 := unsafe.Offsetof(x.f)
+       _5 := unsafe.Offsetof((x.f))
+       _6 := unsafe.Offsetof((((((((x))).f)))))
+       unsafe /* ERROR "not used" */ .Offsetof(x.f)
+}
+
+func _Sizeof() {
+       var x int
+       _0 := unsafe /* ERROR "argument" */ .Sizeof()
+       _1 := unsafe /* ERROR "argument" */ .Sizeof(1, 2)
+       _2 := unsafe.Sizeof(int /* ERROR "not an expression" */)
+       _3 := unsafe.Sizeof(42)
+       _4 := unsafe.Sizeof(new(complex128))
+       unsafe /* ERROR "not used" */ .Sizeof(x)
+
+       // basic types have size guarantees
+       assert(unsafe.Sizeof(byte(0)) == 1)
+       assert(unsafe.Sizeof(uint8(0)) == 1)
+       assert(unsafe.Sizeof(int8(0)) == 1)
+       assert(unsafe.Sizeof(uint16(0)) == 2)
+       assert(unsafe.Sizeof(int16(0)) == 2)
+       assert(unsafe.Sizeof(uint32(0)) == 4)
+       assert(unsafe.Sizeof(int32(0)) == 4)
+       assert(unsafe.Sizeof(float32(0)) == 4)
+       assert(unsafe.Sizeof(uint64(0)) == 8)
+       assert(unsafe.Sizeof(int64(0)) == 8)
+       assert(unsafe.Sizeof(float64(0)) == 8)
+       assert(unsafe.Sizeof(complex64(0)) == 8)
+       assert(unsafe.Sizeof(complex128(0)) == 16)
+}
+
+// self-testing only
+func _assert() {
+       var x int
+       assert /* ERROR "argument" */ ()
+       assert /* ERROR "argument" */ (1, 2)
+       assert("foo" /* ERROR "boolean constant" */ )
+       assert(x /* ERROR "boolean constant" */)
+       assert(true)
+       assert /* ERROR "failed" */ (false) 
+}
+
+// self-testing only
+func _trace() {
+       // Uncomment the code below to test trace - will produce console output
+       // _0 := trace /* ERROR "no value" */ ()
+       // _1 := trace(1)
+       // _2 := trace(true, 1.2, '\'', "foo", 42i, "foo" <= "bar")
+}