package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"log"
)

// !+input
const input = `
package main

var m = make(map[string]int)

func main() {
	v, ok := m["hello, " + "world"]
	print(rune(v), ok)
}
`

//!-input

var fset = token.NewFileSet()

func main() {
	f, err := parser.ParseFile(fset, "hello.go", input, 0)
	if err != nil {
		log.Fatal(err) // parse error
	}

	conf := types.Config{Importer: importer.Default()}
	info := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
	if _, err := conf.Check("cmd/hello", fset, []*ast.File{f}, info); err != nil {
		log.Fatal(err) // type error
	}

	//!+inspect
	// f is a parsed, type-checked *ast.File.
	ast.Inspect(f, func(n ast.Node) bool {
		if expr, ok := n.(ast.Expr); ok {
			if tv, ok := info.Types[expr]; ok {
				fmt.Printf("%-24s\tmode:  %s\n", nodeString(expr), mode(tv))
				fmt.Printf("\t\t\t\ttype:  %v\n", tv.Type)
				if tv.Value != nil {
					fmt.Printf("\t\t\t\tvalue: %v\n", tv.Value)
				}
			}
		}
		return true
	})
	//!-inspect
}

// nodeString formats a syntax tree in the style of gofmt.
func nodeString(n ast.Node) string {
	var buf bytes.Buffer
	format.Node(&buf, fset, n)
	return buf.String()
}

// mode returns a string describing the mode of an expression.
func mode(tv types.TypeAndValue) string {
	s := ""
	if tv.IsVoid() {
		s += ",void"
	}
	if tv.IsType() {
		s += ",type"
	}
	if tv.IsBuiltin() {
		s += ",builtin"
	}
	if tv.IsValue() {
		s += ",value"
	}
	if tv.IsNil() {
		s += ",nil"
	}
	if tv.Addressable() {
		s += ",addressable"
	}
	if tv.Assignable() {
		s += ",assignable"
	}
	if tv.HasOk() {
		s += ",ok"
	}
	return s[1:]
}

/*
//!+output
$ go build golang.org/x/example/gotypes/typeandvalue
$ ./typeandvalue
make(map[string]int)            mode:  value
                                type:  map[string]int
make                            mode:  builtin
                                type:  func(map[string]int) map[string]int
map[string]int                  mode:  type
                                type:  map[string]int
string                          mode:  type
                                type:  string
int                             mode:  type
                                type:  int
m["hello, "+"world"]            mode:  value,assignable,ok
                                type:  (int, bool)
m                               mode:  value,addressable,assignable
                                type:  map[string]int
"hello, " + "world"             mode:  value
                                type:  string
                                value: "hello, world"
"hello, "                       mode:  value
                                type:  untyped string
                                value: "hello, "
"world"                         mode:  value
                                type:  untyped string
                                value: "world"
print(rune(v), ok)              mode:  void
                                type:  ()
print                           mode:  builtin
                                type:  func(rune, bool)
rune(v)                         mode:  value
                                type:  rune
rune                            mode:  type
                                type:  rune
...more not shown...
//!-output
*/
