如何在 Go 中获取一个类型的所有常量
How to get all constants of a type in Go
这是一个例子:
package main
type State int
const (
Created State = iota
Modified
Deleted
)
func main() {
// Some code here where I need the list
// of all available constants of this type.
}
此用例是创建有限状态机 (FSM)。能够获取所有常量将帮助我编写一个测试用例,以确保每个新值在 FSM 映射中都有对应的条目。
无法在运行时执行此操作,因为反射包不能用于它。您可以定义一个列表:
const(
Created State = iota
Modified
Deleted
)
var allStates = []State{Created, Modified, Deleted}
您可以更进一步,添加字符串表示形式或任意数量的其他内容。
您也许可以从源代码中生成这样一个列表,以便于维护,但我通常认为这样节省的时间不值得。像 stringer 这样的工具已经可以完成其中的一些工作。
如果你的常量都是有序的,你可以这样使用:
type T int
const (
TA T = iota
TB
TC
NumT
)
func AllTs() []T {
ts := make([]T, NumT)
for i := 0; i < int(NumT); i++ {
ts[i] = T(i)
}
return ts
}
您还可以将输出缓存在例如init()
。这仅在所有常量按顺序使用 iota
初始化时才有效。如果您需要适用于所有情况的东西,请使用显式切片。
package main
import (
"fmt"
)
type State int
const (
Created State = iota
Modified
Deleted
)
func (s State) Name() (name string) {
switch s {
case Created:
name = "created"
case Modified:
name = "modified"
case Deleted:
name = "deleted"
}
return
}
func main() {
states := States()
fmt.Println(states)
}
func States() (states []State) {
state := State(0)
for {
name := state.Name()
if name == "" {
break
}
states = append(states, state)
state++
}
return
}
既然你在谈论 test-case 我假设你有可用的类型以及定义常量的文件。我使用以下方法解决类似的问题 (go playground) :
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"strconv"
"strings"
)
type InterestingType uint64
const const_go = `
package p
type InterestingType uint64
const (
A InterestingType = iota << 1
B
C
)
type UninterestingType int
const (
D UninterestingType = iota
E
)
`
func main() {
constantValues := []InterestingType{}
ConstantsOf("InterestingType", const_go, func(v string) {
value, err := strconv.ParseUint(v, 0, 64)
if err != nil {
log.Fatal(err)
}
constantValues = append(
constantValues, InterestingType(value))
})
fmt.Printf("%#v\n", constantValues)
}
func ConstantsOf(ctype string, file string, value func(string)) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "const.go", file, 0)
if err != nil {
log.Fatal(err)
}
// Obtain type information.
conf := types.Config{Importer: importer.Default()}
info := &types.Info{
Defs: make(map[*ast.Ident]types.Object),
}
_, err = conf.Check("p", fset, []*ast.File{f}, info)
if err != nil {
log.Fatal(err)
}
for _, d := range f.Decls {
for _, s := range d.(*ast.GenDecl).Specs {
v, ok := s.(*ast.ValueSpec)
if !ok {
continue
}
for _, name := range v.Names {
c := info.ObjectOf(name).(*types.Const)
if strings.HasSuffix(c.Type().String(), ctype) {
value(c.Val().ExactString())
}
}
}
}
}
这是一个例子:
package main
type State int
const (
Created State = iota
Modified
Deleted
)
func main() {
// Some code here where I need the list
// of all available constants of this type.
}
此用例是创建有限状态机 (FSM)。能够获取所有常量将帮助我编写一个测试用例,以确保每个新值在 FSM 映射中都有对应的条目。
无法在运行时执行此操作,因为反射包不能用于它。您可以定义一个列表:
const(
Created State = iota
Modified
Deleted
)
var allStates = []State{Created, Modified, Deleted}
您可以更进一步,添加字符串表示形式或任意数量的其他内容。
您也许可以从源代码中生成这样一个列表,以便于维护,但我通常认为这样节省的时间不值得。像 stringer 这样的工具已经可以完成其中的一些工作。
如果你的常量都是有序的,你可以这样使用:
type T int
const (
TA T = iota
TB
TC
NumT
)
func AllTs() []T {
ts := make([]T, NumT)
for i := 0; i < int(NumT); i++ {
ts[i] = T(i)
}
return ts
}
您还可以将输出缓存在例如init()
。这仅在所有常量按顺序使用 iota
初始化时才有效。如果您需要适用于所有情况的东西,请使用显式切片。
package main
import (
"fmt"
)
type State int
const (
Created State = iota
Modified
Deleted
)
func (s State) Name() (name string) {
switch s {
case Created:
name = "created"
case Modified:
name = "modified"
case Deleted:
name = "deleted"
}
return
}
func main() {
states := States()
fmt.Println(states)
}
func States() (states []State) {
state := State(0)
for {
name := state.Name()
if name == "" {
break
}
states = append(states, state)
state++
}
return
}
既然你在谈论 test-case 我假设你有可用的类型以及定义常量的文件。我使用以下方法解决类似的问题 (go playground) :
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"strconv"
"strings"
)
type InterestingType uint64
const const_go = `
package p
type InterestingType uint64
const (
A InterestingType = iota << 1
B
C
)
type UninterestingType int
const (
D UninterestingType = iota
E
)
`
func main() {
constantValues := []InterestingType{}
ConstantsOf("InterestingType", const_go, func(v string) {
value, err := strconv.ParseUint(v, 0, 64)
if err != nil {
log.Fatal(err)
}
constantValues = append(
constantValues, InterestingType(value))
})
fmt.Printf("%#v\n", constantValues)
}
func ConstantsOf(ctype string, file string, value func(string)) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "const.go", file, 0)
if err != nil {
log.Fatal(err)
}
// Obtain type information.
conf := types.Config{Importer: importer.Default()}
info := &types.Info{
Defs: make(map[*ast.Ident]types.Object),
}
_, err = conf.Check("p", fset, []*ast.File{f}, info)
if err != nil {
log.Fatal(err)
}
for _, d := range f.Decls {
for _, s := range d.(*ast.GenDecl).Specs {
v, ok := s.(*ast.ValueSpec)
if !ok {
continue
}
for _, name := range v.Names {
c := info.ObjectOf(name).(*types.Const)
if strings.HasSuffix(c.Type().String(), ctype) {
value(c.Val().ExactString())
}
}
}
}
}