capnproto / go-capnp

Cap'n Proto library and code generator for Go

Home Page:https://capnproto.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Generated code does not compile when a field is named "message" or "segment"

mologie opened this issue Β· comments

After upgrading from alpha.1 to alpha.19 I receive the following new error when compiling generated schema code:

test/example.capnp.go:1607:23: Foo.Message redeclared in this block
	test/example.capnp.go:1592:23: other declaration of Message

Minimal example to reproduce:

#!/bin/sh

mkdir nameclash
cd nameclash

cat <<"EOF" >nameclash.capnp
@0x97aa969ec333a351;

using Go = import "go.capnp";

$Go.package("nameclash");
$Go.import("nameclash");

struct Foo {
  message @0 :Text;
  segment @1 :Text;
}
EOF

cat <<"EOF" >capnpc-go
#!/bin/sh
exec go run capnproto.org/go/capnp/v3/capnpc-go "$@"
EOF
chmod +x capnpc-go
export PATH=$(pwd):$PATH

go mod init nameclash
go get capnproto.org/go/capnp/v3@v3.0.0-alpha.19
curl -sL https://raw.githubusercontent.com/capnproto/go-capnproto2/v3.0.0-alpha.19/std/go.capnp -o go.capnp

capnp compile -ogo nameclash.capnp
go build .

Expected behavior: No error from go build.

Actual output:

$ go build .
# nameclash
./nameclash.capnp.go:58:14: Foo.Message redeclared in this block
	./nameclash.capnp.go:51:14: other declaration of Message
./nameclash.capnp.go:76:14: Foo.Segment redeclared in this block
	./nameclash.capnp.go:55:14: other declaration of Segment

The field "message" appears 8 times in separate places in my code. I'd prefer to avoid changing those all, and rather help improve the library by resolving the name clash.

It appears that names previously taken in alpha.1, such as Size(), were made available again in some version before alpha.19. They now require an explicit cast to capnp.Struct. I assume this was intentional to avoid such exact clashes, and I really like the simplicity of the mechanism.

Could we drop the .Message() and .Segment() wrappers on generated code too, so that an explicit cast is required to access library-internal functions and thus avoid a clash with user-defined field names?

I can open a PR for this if it's OK with you, plus fix up existing usages within this repo. This change would be backwards-incompatible.

Best,

Oliver

Hi Oliver, thanks for reporting the issue! I'm surprised this ever worked, as the .Message() method exists in the alpha.1 release as well. Is it possible that something else changed?

Could we drop the .Message() and .Segment() wrappers on generated code too, so that an explicit cast is required to access library-internal functions and thus avoid a clash with user-defined field names?

I'm hesitant to make this change since it's likely to break a larger number of builds (assuming this indeed worked in alpha.1 - I haven't had time to check, yet). @zenhack ?

Hopefully we'll have a stable API soon-ish, and this kind of nuisance will go away. πŸ˜ƒ

I'm surprised this ever worked, as the .Message() method exists in the alpha.1 release as well. Is it possible that something else changed?

The generated code did indeed change. You can verify this by changing the two instances of ".19" to ".1" in the script to reproduce the issue.

I am attaching the diff for your convenience -- the Message() and Segment() methods are indeed absent in the code generated by v1. I suppose that they instead were available implicitly when Go expanded the inlined capnp.Struct definition.

Show Diff
--- nameclash.capnp.v1.go	2023-01-09 15:56:13
+++ nameclash.capnp.v19.go	2023-01-09 15:57:51
@@ -8,94 +8,107 @@
 	schemas "capnproto.org/go/capnp/v3/schemas"
 )
 
-type Foo struct{ capnp.Struct }
+type Foo capnp.Struct
 
 // Foo_TypeID is the unique identifier for the type Foo.
 const Foo_TypeID = 0xe74a76624a1a436e
 
 func NewFoo(s *capnp.Segment) (Foo, error) {
 	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
-	return Foo{st}, err
+	return Foo(st), err
 }
 
 func NewRootFoo(s *capnp.Segment) (Foo, error) {
 	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
-	return Foo{st}, err
+	return Foo(st), err
 }
 
 func ReadRootFoo(msg *capnp.Message) (Foo, error) {
 	root, err := msg.Root()
-	return Foo{root.Struct()}, err
+	return Foo(root.Struct()), err
 }
 
 func (s Foo) String() string {
-	str, _ := text.Marshal(0xe74a76624a1a436e, s.Struct)
+	str, _ := text.Marshal(0xe74a76624a1a436e, capnp.Struct(s))
 	return str
 }
 
+func (s Foo) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr {
+	return capnp.Struct(s).EncodeAsPtr(seg)
+}
+
+func (Foo) DecodeFromPtr(p capnp.Ptr) Foo {
+	return Foo(capnp.Struct{}.DecodeFromPtr(p))
+}
+
+func (s Foo) ToPtr() capnp.Ptr {
+	return capnp.Struct(s).ToPtr()
+}
+func (s Foo) IsValid() bool {
+	return capnp.Struct(s).IsValid()
+}
+
+func (s Foo) Message() *capnp.Message {
+	return capnp.Struct(s).Message()
+}
+
+func (s Foo) Segment() *capnp.Segment {
+	return capnp.Struct(s).Segment()
+}
 func (s Foo) Message() (string, error) {
-	p, err := s.Struct.Ptr(0)
+	p, err := capnp.Struct(s).Ptr(0)
 	return p.Text(), err
 }
 
 func (s Foo) HasMessage() bool {
-	return s.Struct.HasPtr(0)
+	return capnp.Struct(s).HasPtr(0)
 }
 
 func (s Foo) MessageBytes() ([]byte, error) {
-	p, err := s.Struct.Ptr(0)
+	p, err := capnp.Struct(s).Ptr(0)
 	return p.TextBytes(), err
 }
 
 func (s Foo) SetMessage(v string) error {
-	return s.Struct.SetText(0, v)
+	return capnp.Struct(s).SetText(0, v)
 }
 
 func (s Foo) Segment() (string, error) {
-	p, err := s.Struct.Ptr(1)
+	p, err := capnp.Struct(s).Ptr(1)
 	return p.Text(), err
 }
 
 func (s Foo) HasSegment() bool {
-	return s.Struct.HasPtr(1)
+	return capnp.Struct(s).HasPtr(1)
 }
 
 func (s Foo) SegmentBytes() ([]byte, error) {
-	p, err := s.Struct.Ptr(1)
+	p, err := capnp.Struct(s).Ptr(1)
 	return p.TextBytes(), err
 }
 
 func (s Foo) SetSegment(v string) error {
-	return s.Struct.SetText(1, v)
+	return capnp.Struct(s).SetText(1, v)
 }
 
 // Foo_List is a list of Foo.
-type Foo_List struct{ capnp.List }
+type Foo_List = capnp.StructList[Foo]
 
 // NewFoo creates a new list of Foo.
 func NewFoo_List(s *capnp.Segment, sz int32) (Foo_List, error) {
 	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
-	return Foo_List{l}, err
+	return capnp.StructList[Foo](l), err
 }
 
-func (s Foo_List) At(i int) Foo { return Foo{s.List.Struct(i)} }
-
-func (s Foo_List) Set(i int, v Foo) error { return s.List.SetStruct(i, v.Struct) }
-
-func (s Foo_List) String() string {
-	str, _ := text.MarshalList(0xe74a76624a1a436e, s.List)
-	return str
-}
-
 // Foo_Future is a wrapper for a Foo promised by a client call.
 type Foo_Future struct{ *capnp.Future }
 
-func (p Foo_Future) Struct() (Foo, error) {
-	s, err := p.Future.Struct()
-	return Foo{s}, err
+func (f Foo_Future) Struct() (Foo, error) {
+	p, err := f.Future.Ptr()
+	return Foo(p.Struct()), err
 }
 
-const schema_97aa969ec333a351 = "x\xda\x12\x88s`2d\xdd\xcf\xc8\xc0\x10(\xc2\xca" +
+const schema_97aa969ec333a351 = "x\xda\x12Hp`1\xe4\xdd\xcf\xc8\xc0\x14(\xc2\xca" +
 	"\xf6?\xcfY\xca+\xa9\xcc\xeb9\x83\xa0\x00\xe3\xff\xc0" +
 	"\xc5\xc6\x87\xe7M[5\x9d\x81\x95\x89\x9d\x81A\xf0\xe8" +
 	"\"\xc1\xb3 \xfad9\x83\xee\xff\xbc\xc4\xdc\xd4\xe4\x9c" +
@@ -104,7 +117,7 @@
 	"\x86@\x15f\xc6@\x03&FAFF\x11F\x90\xa0" +
 	".HP\x83\x991\xd0\x84\x89\xb1>7\xb5\xb881" +
 	"=\x95\x91\x87\x81\x89\x91\x87\x81\xb1\xbe85=75" +
-	"\xaf\x04\xc6\x07\x04\x00\x00\xff\xff,\xac\"\xdc"
+	"\xaf\x04\xc6\x07\x04\x00\x00\xff\xff4\xf2\"\xea"
 
 func init() {
 	schemas.Register(schema_97aa969ec333a351,

Thanks, Oliver! I'm on mobile right now, so this is super helpful.

I suppose that they instead were available implicitly when Go expanded the inlined capnp.Struct definition.

Ah yes, that makes sense.

Let's wait for zenhack's input on this, but to be honest, my inclination is to leave this as-is. I understand where you're coming from (and I'm glad you suggested the fix!), but my main concern is that .Message() is an important part of the API, and requiring an explicit cast makes it less discoverable. Moreover, the proposed change is likely to break a larger number of builds.

As much as I hate to play the "alpha card", well... alpha software... πŸ˜•

Alright, thank you for looking into this so far either way Louis, I really appreciate it!

I did not consider the Message and Segment generated methods to be important so far, since there were no references to Message() in this repo (tests still run through with it removed), and only generated code for interface setters referenced Segment(), where a cast would be harmless.

So until I can call my fields "message" I'll just throw this sed-style Uno reverse card right on top of your alpha card. Beats changing schema files until a more permanent solution is available. πŸ™ˆ

exec sed -E -i '' -e "s/(Message|Segment)\(\) \*capnp/\1_() *capnp/;s/s\.Segment\(\)/s.Segment_()/" "$@"

This is a special case of #46; we still need to settle on a systematic way of avoiding name collisions.

I think concretely what changed is we went from generated types embedding capnp.Struct to having their underlying type be capnp.Struct (really StructKind, but...) in #283, which meant that all methods had to be defined explicitly. Previously, we just got these two from embedding Struct, so when the code generator generated a getter for a field called Message, it would just override those methods.

I think I'm with @lthibault about not changing this at least as a one-off; I probably have a large number of call sites that would be broken by this myself.

My inclination is to close this as a duplicate of #46, and just deal with the general case.

Sorry for the duplicate, I indeed missed the more general ticket! This also let me find $Go.name(), which seems to be helpful for some collisions even if it generates a bit of clutter.

Sorry for the duplicate

No worries! I'm glad you opened the issue πŸ˜ƒ

This also let me find $Go.name(), which seems to be helpful for some collisions even if it generates a bit of clutter.

Excellent! By the way, please feel free to ping us on Matrix if you encounter any API edge-cases in the future. Sometimes it's easier to find workarounds (or scope a PR) in real-time.

See you around πŸ‘‹