Understanding parsed Go types

I am trying to write a custom linter that operates on a specific type. But it seems I am misunderstanding something about Go types. In the example below, I am parsing this example code which declares a var Foo of type time.Time. However, when I compare Foo's type to time.Time using types.Identical(), it returns false. Why is this? If my goal is to find expressions which involve the type time.Time, should I identify them using a different method than types.Identical()?

package main

import (
    "fmt"
    "go/ast"
    "go/importer"
    "go/parser"
    "go/token"
    "go/types"

    "golang.org/x/tools/go/packages"
)

const example = `package main

import "time"

var Foo time.Time

func main() {
}`

// Based on https://github.com/golang/example/tree/master/gotypes#an-example
func ParseExamplePackage() *types.Package {
    fset := token.NewFileSet()

    f, err := parser.ParseFile(fset, "example.go", example, 0)
    if err != nil {
        panic(err)
    }

    conf := types.Config{Importer: importer.Default()}
    pkg, err := conf.Check("example", fset, []*ast.File{f}, nil)
    if err != nil {
        panic(err)
    }

    return pkg
}

func ParseTimePackage() *packages.Package {
    cfg := &packages.Config{
        Mode:       packages.LoadAllSyntax,
    }
    loadedPackages, err := packages.Load(cfg, "time")
    if err != nil {
        panic(err)
    }

    return loadedPackages[0]
}

func main() {
    timePkg := ParseTimePackage()
    timeObject := timePkg.Types.Scope().Lookup("Time")

    examplePkg := ParseExamplePackage()
    fooObject := examplePkg.Scope().Lookup("Foo")

    fmt.Printf("time type: %s\n", timeObject.Type().String()) // time.Time
    fmt.Printf("foo type: %s\n", fooObject.Type().String()) // time.Time
    fmt.Printf("identical: %t\n", types.Identical(timeObject.Type(), fooObject.Type())) // false
}

The only problem with your code is that you’re loading your code and the time package separately. Some of the checks in types.Identical check for *types.Package == *types.Package which evaluates to false if the packages were parsed separately. If you put your example code into a package and load it and the time package together, it works:

package main

import (
	"fmt"
	"go/types"

	"golang.org/x/tools/go/packages"
)

func ParseTestAndTimePackages() (test, time *packages.Package) {
	cfg := &packages.Config{
		Mode: packages.LoadAllSyntax,
	}
	loadedPackages, err := packages.Load(cfg, "forum.golangbridge.org/understanding-parsed-go-types_20561/test", "time")
	if err != nil {
		panic(err)
	}
	if len(loadedPackages) != 2 {
		panic("only loaded 1 pkg")
	}
	if loadedPackages[0].PkgPath == "time" {
		return loadedPackages[1], loadedPackages[0]
	}
	return loadedPackages[0], loadedPackages[1]
}

func main() {
	testPkg, timePkg := ParseTestAndTimePackages()
	timeObject := timePkg.Types.Scope().Lookup("Time")
	fooObject := testPkg.Types.Scope().Lookup("Foo")

	fmt.Printf("time type: %s\n", timeObject.Type().String())                           // time.Time
	fmt.Printf("foo type: %s\n", fooObject.Type().String())                             // time.Time
	fmt.Printf("identical: %t\n", types.Identical(timeObject.Type(), fooObject.Type())) // true
}

1 Like