12

I'm stumped on this one. In the project that I'm working on, we generate go code from Thrift. The code gets created in the package A/B/thriftapi (which used to be A/B/thrift which was causing problems because all of the generated code was importing git.apache.org/thrift.git/lib/go/thrift and causing name collisions).

I generated the code and moved the code into $GOPATH/src/A/B/D I then tried to build my project and was getting tons of errors of the form:

p.X.Read undefined (type Foo has no field or method Read)

I looked at one of the offending lines:

import (
    "A/B/D"
    "git.apache.org/thrift.git/lib/go/thrift"
)

func(p *Bar) readField1(iprot thrift.TProtocol) error {
    p.X = D.NewFoo()
    if err := p.X.Read(iprot); err != nil { 
    ...
 }

Since I am using IntelliJ, I CTRL+clicked on the Read() method and sure enough it jumps to $GOPATH/A/B/D/ttypes.go to the method

func (p *Foo) Read(iprot thrift.TProtocol) error {
    ...
}

That's exactly the file I'd expect the method to be in, and it's a method on a pointer to Foo so no problems there. Everything seems like it should be right, but both in IntelliJ and on the command line I get these problems.

Any ideas what might be going wrong? It's frustrating when it tells me the method doesn't exist, yet will take me right to it if I click on it (and also pops up in the intellisense)

EDIT - Per comment

type Bar struct {
   X Foo `thrift:"x,1,required"`    
}
FuriousGeorge
  • 4,561
  • 5
  • 29
  • 52
  • Which version of the Go plugin are you using? – yole Mar 06 '15 at 21:29
  • What is the type of p.X? – Arjan Mar 06 '15 at 21:30
  • Alpha 164. It also happens on the command line – FuriousGeorge Mar 06 '15 at 21:30
  • Can you add Bar struct definition? – Grzegorz Żur Mar 06 '15 at 21:36
  • Odd. NewFoo() is returning a `*Foo` but is being stored in `Bar.X`. `Bar.X` is a `Foo` NOT a `*Foo. This is particularly strange, as this is generated code. – FuriousGeorge Mar 06 '15 at 21:37
  • Note, that if I modify the code (which isn't a viable solution) to be `(&(p.X)).Read(iprot)` I get an error saying `(&p.X).Read undefined (type *Foo has no field or method Read` (which it does) – FuriousGeorge Mar 06 '15 at 21:42
  • 1
    How exactly is this an IntelliJ-related issue? Both it and the command line are giving you the same errors, right? – Makoto Mar 06 '15 at 22:02
  • It's not an intelliJ related issue. I merely mention IntellJ because it is smart enough to find the method that Go says doesn't exist – FuriousGeorge Mar 06 '15 at 22:13
  • I figured out the `NewFoo` part of the mystery. Thrift creates an alias `type Foo *D.Foo` in the `ttypes.go` file – FuriousGeorge Mar 06 '15 at 22:14
  • 1
    `type` in Go doesn't declare an alias like `typedef` does in C; instead, it's a new type that doesn't carry the methods of the old. If you have two types named Foo, maybe only one has that `Read`. – twotwotwo Mar 07 '15 at 08:35
  • 'Nother thing I thought of, before reading your last comment, was that maybe the plugin and Go compiler are looking in different paths--not familiar with the plugin, so not sure exactly how you check that. – twotwotwo Mar 07 '15 at 08:37
  • It would help if we had the IDL code you talk about. There has been a lot of work on the Go support both in 0.9.2 and in current dev trunk. Make sure you use one of the more recent versions, and more important, make sure compiler and library version do match. The namespace prefixes can be adjusted to some extent, type `thrift --help` to get all the options available. – JensG Mar 07 '15 at 10:16
  • It was generated with 0.9.1. Let me try 0.9.2 and see if that fixes it. – FuriousGeorge Mar 07 '15 at 15:40
  • Type `*D.Foo` may not be the same as `*Foo`. The receiver of `Read()` is `*Foo`, which means only an instance of `*Foo` or `*Bar` (that has `Foo` embedded) can call the method. – Pandemonium Mar 07 '15 at 23:53

3 Answers3

12

Here's a minimal reproduction of what you're seeing.

package main

type A struct{}

type B *A

func (a *A) Read() {}

func main() {
    var b B
    b.Read()
}

Compiling produces this error message:

prog.go:11: b.Read undefined (type B has no field or method Read)

The problem is that thrift is defining it's own Foo which is *D.Foo, and that's the type of b.X. The D.Foo type is represented by A in my code, and the Foo type that thrift introduces is represented by B. Although *D.Foo has a Read() method, the Foo alias doesn't. That's why you're seeing the error message. The error message is confusing in your case because Foo is ambiguously referring to D.Foo or the thrift alias in the text of the error — the compiler means one of them, and you're interpreting it to mean the other.

You can work through the alias by converting the value to the right type by writing (*D.Foo)(b.X).Read() or in the reproduction case:

package main

type A struct{}

type B *A

func (a *A) Read() {}

func main() {
    var b B
    (*A)(b).Read()
}
Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • This is a fairly accurate summation of the problem, however, I can't simply add the typecasting because the code in error is generated by Thrift. – FuriousGeorge Mar 08 '15 at 17:35
  • Why does the same thing seem to happen if you swap the `*A` with `B` in the `Read` method? What would you change in that scenario? – kendfss Oct 15 '21 at 21:42
2

Please do not vote down, but I want to express a simple code to reproduce what you might have encountered. (I have no experience with Thrift, but I think it has more to do with packages)

package main
import (
    "fmt"
    "D"
)

type Foo struct {}

func (f *Foo) PrintIt() {
    fmt.Println("Sample printing")
}

type Bar struct {
    // For the sake of this experiment
    X *D.Foo
}

func (b *Bar) PrintFromBar() {
    // simulates b.x = D.NewFoo()
    b.X = new(D.Foo) 
    b.X.PrintIt()   // The culprit happens here
}

func main() {
    b := new(Bar)
    b.PrintFromBar()
}

The D package:

package D

type Foo struct {}

b.PrintFromBar() fails with "b.X.PrintIt undefined (type *D.Foo has no field or method PrintIt).

The problem might lies in D.NewFoo() creating an alias named Foo for *D.Foo. In your case since your Read() method is already inside the D package, I have no idea without the full code. However, it's interesting that this actually produce the same error.

Pandemonium
  • 7,724
  • 3
  • 32
  • 51
1

As @Anyonymous points out, this is a problem with thrift aliasing and using the wrong one. I consider this a bug in the Thrift compiler (in 0.9.2 and current HEAD) in that it will generate code that will never work. We haven't run into this problem with other languages, just go. Here is a simplification to reproduce the problem:

// Base.thrift
namespace go a.X.c
struct Foo {
    1: required string s
}

and the depenant file

// Child.thrift
namespace go a.Y.c
include "Base.thrift"

typedef Base.Foo Foo // <---- This is what causes the problem

struct Bar {
    1:Foo f  // <-- Will error
    // 1:Base.Foo f   Need to comment out typedef and use this instead
}

Compiling the thrift as is will be fine, but when you go to install the a.Y.c package will produce:

/scratch/go/src/a/Y/c/ttypes.go:78: cannot use c.Foo literal (type *c.Foo) as type *Foo in assignment
/scratch/go/src/a/Y/c/ttypes.go:79: p.F.Read undefined (type *Foo has no field or method Read)
/scratch/go/src/a/Y/c/ttypes.go:105: p.F.Write undefined (type *Foo has no field or method Write)

If I comment out the typedef and swap the lines in Bar then everything works fine. This appears to only happen in Go.

FuriousGeorge
  • 4,561
  • 5
  • 29
  • 52