[ 
https://issues.apache.org/jira/browse/THRIFT-6197?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Sylwester Lachiewicz updated THRIFT-6197:
-----------------------------------------
    Description: 
The Go generator does not resolve typedefs to their underlying definition when 
it emits field types, so any typedef of a struct, and any typedef used before 
it is declared, produces Go code that does not compile.

Verified with the released 0.24.0 compiler; the generated packages were built 
with Go 1.27 against the current lib/go/thrift.

h3. Case 1: typedef declared after first use

{code}
struct Bar {
  1: optional Foo foo,
}
typedef i32 Foo
{code}

The field takes the underlying type rather than the alias, and the read path 
then assigns a *Foo to it:

{code}
type Foo int32
type Bar struct {
  Foo *int32 `thrift:"foo,1" db:"foo" json:"foo,omitempty"`
}
{code}

{noformat}
t5601.go:108:11: cannot use &temp (value of type *Foo) as *int32 value in 
assignment
{noformat}

This is THRIFT-5601, and the field-type half of it is THRIFT-5489.

h3. Case 2: typedef of a struct in the same file

{code}
struct Inner { 1: required string s }
typedef Inner InnerAlias
struct Outer { 1: optional InnerAlias a }
service Svc { InnerAlias get(1: InnerAlias x) }
{code}

The alias is generated as a pointer type, and the field then adds a second 
pointer:

{code}
type InnerAlias *Inner
...
A *InnerAlias `thrift:"a,1" db:"a" json:"a,omitempty"`
{code}

{noformat}
t4901.go:247:8:  cannot use &Inner{} (value of type *Inner) as *InnerAlias 
value in assignment
t4901.go:248:16: p.A.Read undefined (type *InnerAlias has no field or method 
Read)
t4901.go:282:17: p.A.Write undefined (type *InnerAlias has no field or method 
Write)
t4901.go:298:10: p.A.Equals undefined (type *InnerAlias has no field or method 
Equals)
t4901.go:472:40: cannot use args.X (variable of type *InnerAlias) as InnerAlias 
value in argument to p.handler.Get
{noformat}

This is THRIFT-4901, and the service-signature errors are THRIFT-3491.

h3. Case 3: typedef of a struct from an included file

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

// Child.thrift
namespace go a.Y.c
include "Base.thrift"
typedef Base.Foo Foo
struct Bar { 1: Foo f }
{code}

{noformat}
Child.go:107:8:  cannot use &c.Foo{} (value of type *"a/X/c".Foo) as *Foo value 
in assignment
Child.go:108:16: p.F.Read undefined (type *Foo has no field or method Read)
Child.go:141:16: p.F.Write undefined (type *Foo has no field or method Write)
{noformat}

This is THRIFT-3037, open since 2015.

h3. Why one ticket

All three come from the same place: the generator decides a field's Go type 
from the typedef itself instead of from the type it resolves to, and separately 
emits a pointer alias for a struct typedef. THRIFT-5601, THRIFT-5489, 
THRIFT-4901, THRIFT-3491 and THRIFT-3037 are five views of that.

A fix should cover, at minimum: a typedef declared after use, a typedef of a 
struct in the same file, a typedef of a struct across an include, and a typedef 
of a container. The THRIFT-5601 fix was reverted once by THRIFT-5685, so the 
regression coverage matters as much as the fix: a struct declared after its 
first use has to keep a pointer getter, or bar.GetBar().GetFoo() stops 
compiling.

h3. Scope of a fix

Generating a struct typedef as a Go alias (type Alias = Inner) rather than a 
defined type only changes output that does not compile today, so it carries 
almost nothing working with it. Measured on PR #3812 by regenerating every IDL 
under test/, lib/go/test/ and tutorial/ with the released generator and with 
the patched one: 3 of 325 generated files differ, and every changed line is a 
typedef-of-struct declaration.

The one place working code does change is a typedef of a struct that the IDL 
declares but never uses. That compiles today, so hand-written Go can hold such 
a value, and the pointer moves from inside the alias to outside it. That is 
worth a release note.


  was:
The Go generator does not resolve typedefs to their underlying definition when 
it emits field types, so any typedef of a struct, and any typedef used before 
it is declared, produces Go code that does not compile.

Verified with the released 0.24.0 compiler; the generated packages were built 
with Go 1.27 against the current lib/go/thrift.

h3. Case 1: typedef declared after first use

{code}
struct Bar {
  1: optional Foo foo,
}
typedef i32 Foo
{code}

The field takes the underlying type rather than the alias, and the read path 
then assigns a *Foo to it:

{code}
type Foo int32
type Bar struct {
  Foo *int32 `thrift:"foo,1" db:"foo" json:"foo,omitempty"`
}
{code}

{noformat}
t5601.go:108:11: cannot use &temp (value of type *Foo) as *int32 value in 
assignment
{noformat}

This is THRIFT-5601, and the field-type half of it is THRIFT-5489.

h3. Case 2: typedef of a struct in the same file

{code}
struct Inner { 1: required string s }
typedef Inner InnerAlias
struct Outer { 1: optional InnerAlias a }
service Svc { InnerAlias get(1: InnerAlias x) }
{code}

The alias is generated as a pointer type, and the field then adds a second 
pointer:

{code}
type InnerAlias *Inner
...
A *InnerAlias `thrift:"a,1" db:"a" json:"a,omitempty"`
{code}

{noformat}
t4901.go:247:8:  cannot use &Inner{} (value of type *Inner) as *InnerAlias 
value in assignment
t4901.go:248:16: p.A.Read undefined (type *InnerAlias has no field or method 
Read)
t4901.go:282:17: p.A.Write undefined (type *InnerAlias has no field or method 
Write)
t4901.go:298:10: p.A.Equals undefined (type *InnerAlias has no field or method 
Equals)
t4901.go:472:40: cannot use args.X (variable of type *InnerAlias) as InnerAlias 
value in argument to p.handler.Get
{noformat}

This is THRIFT-4901, and the service-signature errors are THRIFT-3491.

h3. Case 3: typedef of a struct from an included file

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

// Child.thrift
namespace go a.Y.c
include "Base.thrift"
typedef Base.Foo Foo
struct Bar { 1: Foo f }
{code}

{noformat}
Child.go:107:8:  cannot use &c.Foo{} (value of type *"a/X/c".Foo) as *Foo value 
in assignment
Child.go:108:16: p.F.Read undefined (type *Foo has no field or method Read)
Child.go:141:16: p.F.Write undefined (type *Foo has no field or method Write)
{noformat}

This is THRIFT-3037, open since 2015.

h3. Why one ticket

All three come from the same place: the generator decides a field's Go type 
from the typedef itself instead of from the type it resolves to, and separately 
emits a pointer alias for a struct typedef. THRIFT-5601, THRIFT-5489, 
THRIFT-4901, THRIFT-3491 and THRIFT-3037 are five views of that. THRIFT-5463 is 
likely the same, though it was not reproduced here.

A fix should cover, at minimum: a typedef declared after use, a typedef of a 
struct in the same file, a typedef of a struct across an include, and a typedef 
of a container. Note that the THRIFT-5601 fix was reverted once before, so the 
regression coverage matters as much as the fix.



> Go generator mishandles typedefs, so aliased structs and forward-declared 
> typedefs generate code that does not compile
> ----------------------------------------------------------------------------------------------------------------------
>
>                 Key: THRIFT-6197
>                 URL: https://issues.apache.org/jira/browse/THRIFT-6197
>             Project: Thrift
>          Issue Type: Bug
>          Components: Go - Compiler
>            Reporter: Sylwester Lachiewicz
>            Priority: Major
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> The Go generator does not resolve typedefs to their underlying definition 
> when it emits field types, so any typedef of a struct, and any typedef used 
> before it is declared, produces Go code that does not compile.
> Verified with the released 0.24.0 compiler; the generated packages were built 
> with Go 1.27 against the current lib/go/thrift.
> h3. Case 1: typedef declared after first use
> {code}
> struct Bar {
>   1: optional Foo foo,
> }
> typedef i32 Foo
> {code}
> The field takes the underlying type rather than the alias, and the read path 
> then assigns a *Foo to it:
> {code}
> type Foo int32
> type Bar struct {
>   Foo *int32 `thrift:"foo,1" db:"foo" json:"foo,omitempty"`
> }
> {code}
> {noformat}
> t5601.go:108:11: cannot use &temp (value of type *Foo) as *int32 value in 
> assignment
> {noformat}
> This is THRIFT-5601, and the field-type half of it is THRIFT-5489.
> h3. Case 2: typedef of a struct in the same file
> {code}
> struct Inner { 1: required string s }
> typedef Inner InnerAlias
> struct Outer { 1: optional InnerAlias a }
> service Svc { InnerAlias get(1: InnerAlias x) }
> {code}
> The alias is generated as a pointer type, and the field then adds a second 
> pointer:
> {code}
> type InnerAlias *Inner
> ...
> A *InnerAlias `thrift:"a,1" db:"a" json:"a,omitempty"`
> {code}
> {noformat}
> t4901.go:247:8:  cannot use &Inner{} (value of type *Inner) as *InnerAlias 
> value in assignment
> t4901.go:248:16: p.A.Read undefined (type *InnerAlias has no field or method 
> Read)
> t4901.go:282:17: p.A.Write undefined (type *InnerAlias has no field or method 
> Write)
> t4901.go:298:10: p.A.Equals undefined (type *InnerAlias has no field or 
> method Equals)
> t4901.go:472:40: cannot use args.X (variable of type *InnerAlias) as 
> InnerAlias value in argument to p.handler.Get
> {noformat}
> This is THRIFT-4901, and the service-signature errors are THRIFT-3491.
> h3. Case 3: typedef of a struct from an included file
> {code}
> // Base.thrift
> namespace go a.X.c
> struct Foo { 1: required string s }
> // Child.thrift
> namespace go a.Y.c
> include "Base.thrift"
> typedef Base.Foo Foo
> struct Bar { 1: Foo f }
> {code}
> {noformat}
> Child.go:107:8:  cannot use &c.Foo{} (value of type *"a/X/c".Foo) as *Foo 
> value in assignment
> Child.go:108:16: p.F.Read undefined (type *Foo has no field or method Read)
> Child.go:141:16: p.F.Write undefined (type *Foo has no field or method Write)
> {noformat}
> This is THRIFT-3037, open since 2015.
> h3. Why one ticket
> All three come from the same place: the generator decides a field's Go type 
> from the typedef itself instead of from the type it resolves to, and 
> separately emits a pointer alias for a struct typedef. THRIFT-5601, 
> THRIFT-5489, THRIFT-4901, THRIFT-3491 and THRIFT-3037 are five views of that.
> A fix should cover, at minimum: a typedef declared after use, a typedef of a 
> struct in the same file, a typedef of a struct across an include, and a 
> typedef of a container. The THRIFT-5601 fix was reverted once by THRIFT-5685, 
> so the regression coverage matters as much as the fix: a struct declared 
> after its first use has to keep a pointer getter, or bar.GetBar().GetFoo() 
> stops compiling.
> h3. Scope of a fix
> Generating a struct typedef as a Go alias (type Alias = Inner) rather than a 
> defined type only changes output that does not compile today, so it carries 
> almost nothing working with it. Measured on PR #3812 by regenerating every 
> IDL under test/, lib/go/test/ and tutorial/ with the released generator and 
> with the patched one: 3 of 325 generated files differ, and every changed line 
> is a typedef-of-struct declaration.
> The one place working code does change is a typedef of a struct that the IDL 
> declares but never uses. That compiles today, so hand-written Go can hold 
> such a value, and the pointer moves from inside the alias to outside it. That 
> is worth a release note.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to