使用go/ast处理iota声明

5

我一直在使用go/ast解析go源代码,并将其作为供应商练习的一部分复制到另一个文件中。我已经处理了大部分内容 - 函数、类型等等 - 但我遇到了使用iota的常量声明问题。我正在遍历ast.File.Scope.Objects中的项,并复制具有Scope.Outer == nil和其Decl == ast.ValueSpec源代码的对象,基本上意味着顶级变量和常量。

在一个块类型中:

const (
    a = iota
    b
    c
    d
)

......每个常量都会被视为一个独立的对象,这是可以理解的。然而,我很难为它们分配值,因为当我迭代它们时,这些对象也可能是无序的。我能通过ast.Object.Data看到其值,但是当它被设置为1 << iota等值时,也似乎存在问题。有没有人知道如何获得正确的iota值分组常量声明?

谢谢!


1
你可以使用 go/types 对 AST 进行类型检查,该包具有 TypeAndValue 结构体,您可以使用它来获取常量的值。 - mkopriva
3
go/ast包维护源代码顺序。您可以直接复制常量值,而无需评估iota。正如前面的评论所述,使用go/types来评估iota。 - Charlie Tumahai
谢谢,我会尝试一下,通过 go/types。 - Aditya
@CeriseLimón 目前我正在迭代 ast.File.Scope.Objects 中的项,但我发现它们不是按顺序排列的。在每次迭代中,我还会检查 Scope.Outer == nil,以验证它是否为全局范围。能否请您指引一下如何按顺序获取这些节点?谢谢! - Aditya
@CeriseLimón 谢谢您的评论,我遇到了这个问题(与 OP 相同),您的评论帮助我找到了解决方案。最终我做了两次循环:一次在作用域对象列表上(获取 const 值),一次在声明列表上以获取正确顺序并解析类型名称。 - hasen
显示剩余2条评论
2个回答

3
我正在为 exhaustive 分析器解决这个问题。在枚举发现阶段,需要查找常量值。

Playground: https://play.golang.org/p/nZLmgE4rJZH

考虑以下 ConstDecl。它由 3 个 ConstSpec 组成,每个 ConstSpec 都有 2 个名称。(此示例使用 iota,但下面的方法通常适用于任何 ConstDecl。)

package example

const (
    A, B = iota, iota * 100 // 0, 0
    _, D                    // 1, 100
    E, F                    // 2, 200
)

使用 go/astgo/types(或 x/tools/go/packages),我们可以获得代表上述 ConstDecl 的 *ast.GenDecl 和该包的 *types.Info。

var decl *ast.GenDecl
var info *types.Info

以下是关于decl的真实情况。
decl.Tok == token.CONST

为了获得常量值,我们可以进行以下操作:
func printValuesConst(decl *ast.GenDecl, info *types.Info) {
    for _, s := range decl.Specs {
        v := s.(*ast.ValueSpec) // safe because decl.Tok == token.CONST
        for _, name := range v.Names {
            c := info.ObjectOf(name).(*types.Const)
            fmt.Println(name, c.Val().ExactString())
        }
    }
}

这将会按照预期输出:

A 0
B 0
_ 1
D 100
E 2
F 200

小贴士: 使用var代替const

请注意,上面的代码适用于const块; 对于var块,我们将不得不使用v.Values[i]来获取值(假设索引i存在值)。

Playground: https://play.golang.org/p/f4mYjXvsvHB

decl.Tok == token.VAR

func printValuesVar(decl *ast.GenDecl, info *types.Info) {
    for _, s := range decl.Specs {
        v := s.(*ast.ValueSpec) // safe because decl.Tok == token.VAR
        for i, name := range v.Names {
            if len(v.Values) <= i {
                fmt.Println(name, "(no AST value)")
                continue
            }
            tv := info.Types[v.Values[i]]
            if tv.Value == nil {
                fmt.Println(name, "(not constant value)")
                continue
            }
            fmt.Println(name, tv.Value.ExactString())
        }
    }
}

0
关于*ast.File类型:
  • Scope.Objects告诉您常量声明的名称和值,但没有按正确顺序排列,因此无法用它来推断类型。

  • Decls按顺序提供声明,因此您可以使用它来推断类型。

您可以在两个列表上进行迭代。首先在Scope.Objects上获取常量值,然后在Decls上推断类型。

游乐场上的示例代码:https://go.dev/play/p/yEz6G1Kqoe3

我将提供以下示例代码。为了使其更具体化,有两个假设:

  • 我们有一个从解析器包中获得的file *ast.File(例如,通过调用parser.ParseDir(..)

  • 我们有一个要获取常量的枚举类型名称列表。

type EnumInfo struct {
    Name     string
    TypeName string // base type
    Consts   []ConstValue
}

type ConstValue struct {
    Name  string
    Value any // int or string
}

我们有一个enumTypesMap map[string]*EnumInfo,它将类型名称映射到EnumInfo对象。

以下代码片段的目的是填充EnumInfo结构体的Consts列表。我们假设我们通过其他方式(例如反射)获得了名称和基本类型名称。

根据您自己的需求进行修改。


type EnumInfo struct {
    Name     string
    TypeName string // base type
    Consts   []ConstValue
}

type ConstValue struct {
    Name  string
    Value any // int or string
}

func PopulateEnumInfo(enumTypesMap map[string]*EnumInfo, file *ast.File) {
    // phase 1: iterate scope objects to get the values
    var nameValues = make(map[string]any)

    for _, object := range file.Scope.Objects {
        if object.Kind == ast.Con {
            nameValues[object.Name] = object.Data
        }
    }

    // phase 2: iterate decls to get the type and names in order
    for _, decl := range file.Decls {
        genDecl, ok := decl.(*ast.GenDecl)
        if !ok {
            continue
        }
        if genDecl.Tok != token.CONST {
            continue
        }
        var enumInfo *EnumInfo
        for _, spec := range genDecl.Specs {
            valSpec := spec.(*ast.ValueSpec)
            if typeIdent, ok := valSpec.Type.(*ast.Ident); ok {
                enumInfo = enumTypesMap[typeIdent.String()]
            }
            if enumInfo != nil {
                for _, nameIdent := range valSpec.Names {
                    name := nameIdent.String()
                    if name == "_" {
                        continue
                    }
                    value := nameValues[name]
                    enumInfo.Consts = append(enumInfo.Consts, ConstValue{
                        Name:  name,
                        Value: value,
                    })
                }
            }
        }
    }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接