You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用golang.org/x/tools/analysis获取实例化泛型类型

问题:使用golang.org/x/tools/analysis获取实例化泛型类型时找不到对应实例

尝试通过pass.TypesInfo.Instances映射获取实例化的泛型类型,但用CallExpr.Fun.(*ast.Ident)作为键时,映射中无对应条目(已确认映射不为空)。

相关代码

调用逻辑

// v is *ast.CallExpr from a type switch

callee := pass.TypesInfo.TypeOf(v.Fun)
if f, ok := callee.(*types.Signature); ok {
    f = expandGenerics(pass, v.Fun, f)
}

泛型展开函数

/// Super simple expander for generics
func expandGenerics(pass *analysis.Pass, callee ast.Expr, s *types.Signature) *types.Signature {
    if s.TypeParams() == nil {
        return s
    }

    if callee, ok := callee.(*ast.Ident); ok {
        log.Printf("Instance: %s\n", callee.Name)
        instance, ok := pass.TypesInfo.Instances[callee]

        if ok {
            log.Printf("Generic: %s\n", TypeString(pass.Pkg, instance.Type))

            if s, ok := instance.Type.(*types.Signature); ok {
                return s
            }

        } else {
            log.Printf("Unknown: %s\n", TypeString(pass.Pkg, s))

            //for key, ty := range pass.TypesInfo.Instances {
            //  log.Printf("Keys: %s (%s)\n", key, TypeString(pass.Pkg, ty.Type))
            //}
        }

    }

    return s
}

我也参考了官方源码,发现它使用的标识符逻辑和我的代码完全一致,对应代码片段如下:

// 获取泛型实例的逻辑
if ident, ok := expr.(*ast.Ident); ok {
    if inst, ok := pkg.GetTypesInfo().Instances[ident]; ok {
        // 处理实例化类型
        targs := inst.TypeArgs
        if len(targs) > 0 {
            // 添加类型参数的内嵌提示
            hints = append(hints, inlayHintForTypeArgs(pkg, ident, targs))
        }
    }
}

问题原因及解决方法

原因

当调用泛型函数(如Foo[int]())时,CallExpr.Fun对应的是*ast.IndexExpr而非直接的*ast.Ident。TypesInfo.Instances的键是实例化节点中指向原始泛型的标识符,也就是IndexExpr.X对应的*ast.Ident,而非CallExpr.Fun直接转换的Ident。

解决方法

修改代码,先解析CallExpr.Fun中可能存在的IndexExpr,提取正确的标识符作为键:

调整泛型展开函数

func expandGenerics(pass *analysis.Pass, callee ast.Expr, s *types.Signature) *types.Signature {
    if s.TypeParams() == nil {
        return s
    }

    // 解析泛型实例化的语法树节点
    var targetIdent *ast.Ident
    switch expr := callee.(type) {
    case *ast.Ident:
        targetIdent = expr
    case *ast.IndexExpr:
        // 泛型调用的Fun是IndexExpr,提取其内部的原始泛型标识符
        if idxIdent, ok := expr.X.(*ast.Ident); ok {
            targetIdent = idxIdent
        }
    }

    if targetIdent != nil {
        log.Printf("Instance: %s\n", targetIdent.Name)
        instance, ok := pass.TypesInfo.Instances[targetIdent]
        if ok {
            log.Printf("Generic: %s\n", TypeString(pass.Pkg, instance.Type))
            if sig, ok := instance.Type.(*types.Signature); ok {
                return sig
            }
        } else {
            log.Printf("Unknown: %s\n", TypeString(pass.Pkg, s))
        }
    }

    return s
}

调整调用逻辑

// v is *ast.CallExpr from a type switch
callee := pass.TypesInfo.TypeOf(v.Fun)
if f, ok := callee.(*types.Signature); ok {
    // 直接传入CallExpr.Fun,让expandGenerics内部处理IndexExpr解析
    f = expandGenerics(pass, v.Fun, f)
}

额外说明

官方源码处理的是直接引用泛型实例(如var f = Foo[int])的场景,此时语法树中Expr是*ast.IndexExpr;而你的场景是调用泛型实例函数,CallExpr.Fun才是*ast.IndexExpr,二者语法树结构不同,但核心都是提取IndexExpr.X对应的标识符。

内容的提问来源于stack exchange,提问作者kdy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:27:26