Thanks for that. I spent some time over the weekend refactoring from raw ast <https://pkg.go.dev/go/ast> parser with a simpleImporter and universe to using types <https://pkg.go.dev/go/types>.
So far this has provided everything I need, and also massively simplified the code as no need to manually parse the ast.File apart from a basic ast.Visitor <https://pkg.go.dev/go/ast#Visitor> to create a lookup map for type comments and position information. I did find the types docs <https://pkg.go.dev/go/types> less consumable than the ast docs <https://pkg.go.dev/go/ast> as the relationship between the various types and the data in types.Info <https://pkg.go.dev/go/types#Info> wasn't particularly clear to me even after reading the tutorial <https://golang.org/s/types-tutorial>. This resulted in it taking quite a bit of trial and error, which I didn't need with raw ast. However once I combined an ast.Visitor <https://pkg.go.dev/go/ast#Visitor> and a pass over info.Defs to create a type name map I had most of all the elements. Getting the type name from types.Info <https://pkg.go.dev/go/types#Info> map had me scratching my head for some time as I couldn't believe that the Types field wouldn't have an easy way to get to the name of the type. Using types Config.Check <https://pkg.go.dev/go/types#Config.Check> didn't have any performance penalties that I saw with packages.Load <https://pkg.go.dev/golang.org/x/tools@v0.1.7/go/packages#Load> so I'm hopeful that this is the correct route. I'd love to hear if there is a neat way to get from info.Types to the name of each type without having to process info.Defs first, but that's more to know if missed something obvious or not 😋 Thanks again to everyone who has provided pointers in this thread, as it demonstrates what great community this is! On Sun, 17 Oct 2021 at 22:16, K. Alex Mills <k.alex.mi...@gmail.com> wrote: > I'm guessing here, but you probably do need the full type-checker for > that, depending on what you mean by "a native type". > > In go we can alias types in two ways. > > type A uint8 > type B = uint8 > > There are subtle differences between the two which I don't claim to > understand... In any case, AFAIK both are essentially built-in types at > runtime. > > Suppose these types are declared in a third-party package on GitHub and > another package uses A and B (since they're exported) to declare a struct > like this. > > import "github.com/foo/bar" > > type MyStruct struct { > Byte1 bar.A > Byte2 bar.B > } > > AFAIK, the compiler cannot tell whether Byte1 and Byte2 are builtin types > or a struct without seeing their definition hosted on GitHub. Which means > downloading, parsing, and type-checking the code from GitHub. > > But it gets a little worse. Type-checking the remote code entails > downloading any of its dependencies recursively and doing the same thing... > I'm sure you can see how this can take some time -- especially for large > projects. > > Anyway, it sounds to me like what you're asking to do requires the use of > the type-checker and because of the dependency management involved I > believe the most convenient route to type-checking (at this time) lives in > the packages package. > > Happy to learn if anyone else knows otherwise. > > On Sun, Oct 17, 2021, 5:00 AM Steven Hartland <ste...@multiplay.co.uk> > wrote: > >> I need to be able to tell the types of fields, in particular are fields >> of a struct a native type or a struct themselves. >> >> The ast parse even with a simple importer don’t provide that info. >> >> On Sat, 16 Oct 2021 at 21:06, 'Richard Oudkerk' via golang-nuts < >> golang-nuts@googlegroups.com> wrote: >> >>> I am not sure what "import external packages" means. >>> >>> Apart dot imports (which I have never seen used for real) why would you >>> need to load the imported packages? >>> >>> On Saturday, 16 October 2021 at 20:34:17 UTC+1 Steven Hartland wrote: >>> >>>> Thanks Richard, that allowed me to replace a hand rolled >>>> universe scope 👍 >>>> >>>> My importer varies from yours in that for correct lookups for versioned >>>> packages or those with '-' in I had to copy ImportPathToAssumedName >>>> from x/tools/internal/imports/fix.go. >>>> >>>> func simpleImporter(imports map[string]*ast.Object, path string) >>>> (*ast.Object, error) { >>>> pkg := imports[path] >>>> if pkg == nil { >>>> pkg = ast.NewObj(ast.Pkg, ImportPathToAssumedName(path)) >>>> pkg.Data = ast.NewScope(nil) // required by >>>> ast.NewPackage for dot-import >>>> imports[path] = pkg >>>> } >>>> return pkg, nil >>>> } >>>> >>>> This now works for all cases which don't import external packages. So >>>> now I just need to do the on demand load of packages, which I suspect will >>>> lead me right back to packages.Load. >>>> >>>> On Sat, 16 Oct 2021 at 15:59, 'Richard Oudkerk' via golang-nuts < >>>> golan...@googlegroups.com> wrote: >>>> >>>>> You could try building the universe scope for ast.NewPackage from >>>>> types.Universe. For example >>>>> >>>>> https://play.golang.org/p/1E5Iu4vW3g9 >>>>> >>>>> func NewPackage(fset *token.FileSet, files map[string]*ast.File) >>>>> (*ast.Package, error) { >>>>> univ, err := universe() >>>>> if err != nil { >>>>> return nil, err >>>>> } >>>>> return ast.NewPackage(fset, files, dummyImporter, univ) >>>>> } >>>>> >>>>> func dummyImporter(imports map[string]*ast.Object, importPath string) >>>>> (*ast.Object, error) { >>>>> pkg := imports[importPath] >>>>> if pkg == nil { >>>>> pkg = ast.NewObj(ast.Pkg, path.Base(importPath)) >>>>> pkg.Data = ast.NewScope(nil) >>>>> imports[importPath] = pkg >>>>> } >>>>> return pkg, nil >>>>> } >>>>> >>>>> func universe() (*ast.Scope, error) { >>>>> u := ast.NewScope(nil) >>>>> for _, name := range types.Universe.Names() { >>>>> o := types.Universe.Lookup(name) >>>>> if o == nil { >>>>> return nil, fmt.Errorf("failed to lookup %s in universe scope", name) >>>>> } >>>>> var objKind ast.ObjKind >>>>> switch o.(type) { >>>>> case *types.Const, *types.Nil: >>>>> objKind = ast.Con >>>>> case *types.TypeName: >>>>> objKind = ast.Typ >>>>> case *types.Builtin: >>>>> objKind = ast.Fun >>>>> default: >>>>> return nil, fmt.Errorf("unexpected builtin %s of type %T", o.Name(), o) >>>>> } >>>>> obj := ast.NewObj(objKind, name) >>>>> if u.Insert(obj) != nil { >>>>> return nil, fmt.Errorf("types internal error: double declaration") >>>>> } >>>>> obj.Decl = u >>>>> } >>>>> return u, nil >>>>> } >>>>> >>>>> On Saturday, 16 October 2021 at 14:38:43 UTC+1 eli...@gmail.com wrote: >>>>> >>>>>> On Fri, Oct 15, 2021 at 2:13 PM Steven Hartland < >>>>>> ste...@multiplay.co.uk> wrote: >>>>>> >>>>>>> I converted my code to x/tools/go/packages >>>>>>> <https://pkg.go.dev/golang.org/x/tools@v0.1.7/go/packages> and >>>>>>> while it did solve the problem it's VERY slow in comparison. >>>>>>> >>>>>>> I have a set of 21 tests operating on a single package which has at >>>>>>> most two very basic types, no imports and using go/parser >>>>>>> <https://pkg.go.dev/go/parser> they take 0.011s but with go/packages >>>>>>> <https://pkg.go.dev/golang.org/x/tools@v0.1.7/go/packages> that >>>>>>> increases to 3.548s a 300x slow down. >>>>>>> >>>>>>> I'm setting a basic mode: packages.NeedName | packages.NeedSyntax >>>>>>> >>>>>>> The package.Load call takes ~220ms whereas ast.NewPackage only >>>>>>> takes 2.7µs. >>>>>>> >>>>>> >>>>>> Could you post a reproducer of your target package and analysis >>>>>> somewhere? 220ms for packages.Load sounds like a lot. It's true that >>>>>> packages does a lot more work than just the parser (*), but it's not >>>>>> supposed to be that slow. In my tests a simple Load with more >>>>>> functionality >>>>>> takes 60-70ms >>>>>> >>>>>> (*) The type checking takes a bit of time over just parsing to AST, >>>>>> but the biggest difference is loading multiple files from imports. For >>>>>> type >>>>>> checking you need to know, when you see: >>>>>> >>>>>> import foo >>>>>> >>>>>> x := foo.Foo() >>>>>> >>>>>> What the type of `x` is, so go/packages has to analyze the `foo` >>>>>> package as well. >>>>>> >>>>>> >>>>>> >>>>>>> >>>>>>> As the resulting ast.File's are pretty much the same, I'm wondering >>>>>>> if for my use case packages.Load is doing way more than I need? >>>>>>> >>>>>>> Another downside is for tests run in a temporary directory outside >>>>>>> of the package space package.Load fails with: >>>>>>> directory /tmp/tests76985775 outside available modules >>>>>>> >>>>>>> I fixed it by calling ioutil.TempDir with "." but that's not ideal. >>>>>>> >>>>>>> Thoughts? >>>>>>> >>>>>>> On Tue, 12 Oct 2021 at 13:42, Steven Hartland < >>>>>>> ste...@multiplay.co.uk> wrote: >>>>>>> >>>>>>>> Thanks David, much appreciated, I will have a look at both. >>>>>>>> >>>>>>>> When migrating from go/ast to go/types did you hit anything of >>>>>>>> note I should look out for? >>>>>>>> >>>>>>>> On Mon, 11 Oct 2021 at 17:06, David Finkel <david....@gmail.com> >>>>>>>> wrote: >>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> On Mon, Oct 11, 2021 at 5:48 AM Steven Hartland < >>>>>>>>> ste...@multiplay.co.uk> wrote: >>>>>>>>> >>>>>>>>>> If the ast.Files passed to ast.NewPackage includes built in types >>>>>>>>>> such as int it returns an error e.g. >>>>>>>>>> file1.go:5:6: undeclared name: int >>>>>>>>>> >>>>>>>>>> Is there a way to prevent that? >>>>>>>>>> >>>>>>>>> >>>>>>>>> Generally, I always add the `builtin` package to the list of >>>>>>>>> packages I'm parsing. >>>>>>>>> I wrote a little library for exactly this kind of package loading >>>>>>>>> a few years ago: >>>>>>>>> https://gitlab.com/dfinkel/goastpkg/-/blob/master/go_ast_parser.go >>>>>>>>> (https://pkg.go.dev/golang.spin-2.net/astpkg) >>>>>>>>> >>>>>>>>>> >>>>>>>>>> Playground example: https://play.golang.org/p/Yg30TTzoLHP >>>>>>>>>> >>>>>>>>>> My goal is to take multiple files, resolve inter file >>>>>>>>>> dependencies e.g. a type referencing another type in a different >>>>>>>>>> file and >>>>>>>>>> process the resulting ast.Files. So if there is a better way to >>>>>>>>>> achieve >>>>>>>>>> this I'm all ears. >>>>>>>>>> >>>>>>>>> >>>>>>>>> In general, I've stopped using the `go/ast` internal references as >>>>>>>>> much and have started using resolved `go/types` references as they're >>>>>>>>> more >>>>>>>>> reliable and better-specified. >>>>>>>>> (golang.org/x/tools/go/packages >>>>>>>>> <https://pkg.go.dev/golang.org/x/tools@v0.1.7/go/packages> has a >>>>>>>>> LoadMode flag for generating `go/types.Info` (NeedTypesInfo >>>>>>>>> <https://pkg.go.dev/golang.org/x/tools@v0.1.7/go/packages#NeedTypesInfo> >>>>>>>>> )) >>>>>>>>> >>>>>>>>>> >>>>>>>>>> Regards >>>>>>>>>> Steve >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> You received this message because you are subscribed to the >>>>>>>>>> Google Groups "golang-nuts" group. >>>>>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>>>>> send an email to golang-nuts...@googlegroups.com. >>>>>>>>>> To view this discussion on the web visit >>>>>>>>>> https://groups.google.com/d/msgid/golang-nuts/CAHEMsqbJoJxuo3c-mofMtzXXJhYCzV2skW2ZB3ZPY6WtA8%2BxHw%40mail.gmail.com >>>>>>>>>> <https://groups.google.com/d/msgid/golang-nuts/CAHEMsqbJoJxuo3c-mofMtzXXJhYCzV2skW2ZB3ZPY6WtA8%2BxHw%40mail.gmail.com?utm_medium=email&utm_source=footer> >>>>>>>>>> . >>>>>>>>>> >>>>>>>>> -- >>>>>>> You received this message because you are subscribed to the Google >>>>>>> Groups "golang-nuts" group. >>>>>>> To unsubscribe from this group and stop receiving emails from it, >>>>>>> send an email to golang-nuts...@googlegroups.com. >>>>>>> >>>>>> To view this discussion on the web visit >>>>>>> https://groups.google.com/d/msgid/golang-nuts/CAHEMsqYMSBUfuOUvptv6UrvBFTwFxjOhJZ5sMN-omOx5ESL5hw%40mail.gmail.com >>>>>>> <https://groups.google.com/d/msgid/golang-nuts/CAHEMsqYMSBUfuOUvptv6UrvBFTwFxjOhJZ5sMN-omOx5ESL5hw%40mail.gmail.com?utm_medium=email&utm_source=footer> >>>>>>> . >>>>>> >>>>>> >>>>>>> -- >>>>> You received this message because you are subscribed to the Google >>>>> Groups "golang-nuts" group. >>>>> To unsubscribe from this group and stop receiving emails from it, send >>>>> an email to golang-nuts...@googlegroups.com. >>>>> >>>> To view this discussion on the web visit >>>>> https://groups.google.com/d/msgid/golang-nuts/d570a7ce-a780-46d8-a323-f9c26a6c2561n%40googlegroups.com >>>>> <https://groups.google.com/d/msgid/golang-nuts/d570a7ce-a780-46d8-a323-f9c26a6c2561n%40googlegroups.com?utm_medium=email&utm_source=footer> >>>>> . >>>>> >>>> -- >>> You received this message because you are subscribed to the Google >>> Groups "golang-nuts" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to golang-nuts+unsubscr...@googlegroups.com. >>> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/golang-nuts/6aaa7c3a-7ef5-47ea-9f29-75443a4599b6n%40googlegroups.com >>> <https://groups.google.com/d/msgid/golang-nuts/6aaa7c3a-7ef5-47ea-9f29-75443a4599b6n%40googlegroups.com?utm_medium=email&utm_source=footer> >>> . >>> >> -- >> You received this message because you are subscribed to the Google Groups >> "golang-nuts" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to golang-nuts+unsubscr...@googlegroups.com. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/CAHEMsqaQ9%3Dia2MXrVEXt--Qk3Nrx1UXK2JYU0D2BxBcZX%2B5mxw%40mail.gmail >> <https://groups.google.com/d/msgid/golang-nuts/CAHEMsqaQ9%3Dia2MXrVEXt--Qk3Nrx1UXK2JYU0D2BxBcZX%2B5mxw%40mail.gmail.com?utm_medium=email&utm_source=footer> >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAHEMsqZ-MjFGA97x3kb3fxJUDRQ_ezcb-%3DjkU1f1yRGoj%3DnPKg%40mail.gmail.com.