go-poet icon indicating copy to clipboard operation
go-poet copied to clipboard

Importing from the same package as a generated file

Open dpolansky opened this issue 8 years ago • 2 comments

In the following example, a file is generated inside of package main while referencing a function, foo, which is also in package main.

package main

import (
    "fmt"

    "github.com/dpolansky/go-poet/poet"
)

func foo() {

}

func main() {
    bar := poet.NewFuncSpec("bar").Statement("$T()", poet.TypeReferenceFromInstance(foo))

    file := poet.NewFileSpec("main").CodeBlock(bar)
    fmt.Println(file)
}

which currently produces

package main

import (
        "main"
)

func bar() {
        main.foo()
}

instead of the expected output

package main

func bar() {
        foo()
}

We should be able to know that foo is in the same package as the generated file and not import itself. In addition, since we only specify the base name of a package rather than the full path, we do not know the full package path of the generated file, making it ambiguous which packages do not need to be imported.

dpolansky avatar Aug 14 '16 05:08 dpolansky

Here's how I'm thinking about it: there are two distinct places where the local package matters:

  • When we build the imports (...) block in FileSpec.String(), the local package should be omitted.
  • When we substitute $T in the template, it should become unqualified if we are writing to the local package.

Right now the API gives back a string and doesn't actually know what we are going to do with the generated code. I think we must get the ImportPath from the user when they create a FileSpec. This will let us change the "external imports" filter to skip the local package in addition to blank packages. The change can actually be made in a somewhat backwards compatible way by saying that the default package is path.Base(f.ImportPath) and changing the package argument on NewFileSpec to be the ImportPath.

The type substitution is a little trickier. At a high level, I think we need to defer converting code to a string until we are somewhere with enough context to change TypeReference qualifiers based on ImportPath. Here's a proposal:

  • Embed Import in TypeReference, adding GetPackage() and GetAlias(). This is the most painful API break as every type implementing the interface will need to be updated.
  • Change CodeBlock's API from String() string to Statements []statement.
  • Add codeWriter.WriteCodeBlock which iterates through the statements and writes them all. Use this as the primary means of writing to a codeWriter (no more w.WriteCode()).
  • Change newCodeWriter and template() take an Import for the import path where they are writing
  • When codeWriter.WriteStatement runs template, it provides its Import
  • When template() encounters $T and calls getQualifiedNameFromArg, it checks currPkg == typeRef.GetPackage().
    • If typeRef is the local package, return the unqualified name for the type.
    • Else if typeRef.GetAlias() != "", use the alias as the qualifier.
    • By default, use path.Base(typeRef.GetPackage()) as the qualifier.
    • Note: I think this allows us to deprecate or get rid of the unqualified prefix stuff unless you think the escape hatch is useful.

End-to-end when I use a type declared in my local package:

  • My FileSpec knows it's local and skips the import
  • My codeWriter knows it's local and unqualifies the name
  • If I reuse the same TypeReference in a FileSpec with a different ImportPath, it will do the right thing and import the type. I think this solves #7 too

thoughts?

bmoylan avatar Feb 20 '17 01:02 bmoylan

Delegating the conversion of CodeBlocks to strings down to the CodeWriter is a great idea. Combined with passing in the full import path, I think this solves the issue.

Could you elaborate more on how you think this approach could solve #7? My understanding is that we still don't know what the ImportPath is for a generated type. With the changes to TypeReference, the GetPackage() method on a newly created spec will be blank. Are you suggesting that we use the wrapping idea combined with the full import path?

dpolansky avatar Feb 20 '17 02:02 dpolansky