Go version

go1.25

Output of go env in your module/workspace:

n/a

What did you do?

Run the following:

package main

import (
    jsonv1 "encoding/json"
    "fmt"
    "net/netip"

    jsonv1in2 "github.com/go-json-experiment/json/v1"
    "tailscale.com/util/must"
)

type HostValue struct {
    netip.Prefix
}

func (p HostValue) MarshalText() ([]byte, error) {
    if p.Prefix.IsSingleIP() {
        return p.Prefix.Addr().MarshalText()
    }
    return p.Prefix.MarshalText()
}

func main() {
    m := map[string]HostValue{
        "example-host-1": HostValue{must.Get(netip.ParsePrefix("100.100.100.100/32"))},
    }
    fmt.Println(string(must.Get(jsonv1.Marshal(&m))))
    fmt.Println(string(must.Get(jsonv1in2.Marshal(&m))))
}

What did you see happen?

{"example-host-1":"100.100.100.100"}
{"example-host-1":"100.100.100.100/32"}

What did you expect to see?

{"example-host-1":"100.100.100.100"}
{"example-host-1":"100.100.100.100"}

It seems that the MarshalText method was not called by the v1 emulation in v2.

Comment From: dsnet

It turns out that this is just a fundamental risk with Go embedding.

type HostValue struct {
    netip.Prefix // forwards both `MarshalText` and `AppendText`
}

func (p HostValue) MarshalText() ([]byte, error) // tries to override MarshalText, but fails to override AppendText

The problem is that the new v1 emulation in v2 respects the new encoding.TextAppender interface, while the v1 implementation does not.

I wonder if this should be a static analysis check that flags the following pattern: * type T embeds type B, which has both B.MarshalXXX, and B.AppendXXX * type T declares T.MarshalXXX, indicating intent to override B.MarshalXXX, but fails to also implement T.AppendXXX

\cc @neild @adonovan

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: dsnet

I'm renaming this since this isn't an encoding/json/v2 issue.

I propose the following static check: * If T embeds E and * E implements special methods M1 and M2 (where both M1 and M2 are semantically equivalent and both understood by common packages; for example MarshalText and AppendText are both understood by encoding/json/v2) and * T only explicitly implements one of the methods (e.g., just M1 or M2) in the set, * then report a violation.

Examples of magic methods which should be semantically equivalent: * having both MarshalText and AppendText * having both MarshalBinary and AppendBinary * having both MarshalJSON and MarshalJSONTo * having both UnmarshalJSON and UnmarshalJSONFrom * having both Read and WriteTo * having both Write and ReadFrom

I'm sure there are other sets of methods, but this comes to mind off the top of my head.

\cc @dominikh

Comment From: gopherbot

Change https://go.dev/cl/704375 mentions this issue: gopls/internal/analysis/methodembed: detect inconsistent overriding