Skip to content

Commit 25fc130

Browse files
authored
Flatten embedded structs whose toml tag only sets options (#1079)
1 parent 2586526 commit 25fc130

2 files changed

Lines changed: 66 additions & 7 deletions

File tree

unmarshaler.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,14 +2238,16 @@ func addFields(plan *structPlan, t reflect.Type, prefix []int) {
22382238
f := t.Field(i)
22392239
tag, tagged := f.Tag.Lookup("toml")
22402240
name := f.Name
2241+
explicitName := ""
22412242
if tagged {
22422243
// A tag of exactly "-" drops the field. "-," names it "-".
22432244
if tag == "-" {
22442245
continue
22452246
}
22462247
parts := strings.SplitN(tag, ",", 2)
2247-
if parts[0] != "" {
2248-
name = parts[0]
2248+
explicitName = parts[0]
2249+
if explicitName != "" {
2250+
name = explicitName
22492251
}
22502252
}
22512253
if f.Anonymous {
@@ -2257,14 +2259,17 @@ func addFields(plan *structPlan, t reflect.Type, prefix []int) {
22572259
// Embedded non-struct fields are not decoded into.
22582260
continue
22592261
}
2260-
if !tagged {
2261-
// Untagged embedded structs are flattened, even when their
2262-
// type is unexported: only their own exported fields are
2263-
// reachable.
2262+
if explicitName == "" {
2263+
// Embedded structs without an explicit tag name are flattened,
2264+
// even when their type is unexported: only their own exported
2265+
// fields are reachable. A tag that only sets options (e.g.
2266+
// `,inline`) still flattens, matching encoding/json and the
2267+
// encoder's behavior.
22642268
embedded = append(embedded, f)
22652269
continue
22662270
}
2267-
// A tagged embedded struct acts as a regular named field.
2271+
// An embedded struct given an explicit tag name acts as a regular
2272+
// named field.
22682273
} else if f.PkgPath != "" {
22692274
// unexported
22702275
continue

unmarshaler_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4296,6 +4296,60 @@ func TestUnmarshalEmbedNonString(t *testing.T) {
42964296
assert.Equal(t, d.Foo, nil)
42974297
}
42984298

4299+
// Regression test for https://github.com/pelletier/go-toml/issues/1078: an
4300+
// embedded struct whose toml tag only sets options (e.g. `,inline`) has no
4301+
// explicit name and must be flattened, just like an untagged embedded struct
4302+
// and just like the encoder does. Before the fix, the fields stayed at their
4303+
// zero values.
4304+
func TestUnmarshalEmbeddedInline(t *testing.T) {
4305+
type Base struct {
4306+
Region string
4307+
Name string
4308+
Port int
4309+
}
4310+
type Config struct {
4311+
Base `toml:",inline"`
4312+
PID uint32
4313+
}
4314+
4315+
doc := `
4316+
Region = "us"
4317+
Name = "srv1"
4318+
Port = 100
4319+
PID = 42
4320+
`
4321+
var c Config
4322+
err := toml.Unmarshal([]byte(doc), &c)
4323+
assert.NoError(t, err)
4324+
assert.Equal(t, Config{
4325+
Base: Base{Region: "us", Name: "srv1", Port: 100},
4326+
PID: 42,
4327+
}, c)
4328+
}
4329+
4330+
// An embedded struct given an explicit tag name keeps acting as a regular
4331+
// named field: its contents come from a sub-table, not from promoted keys.
4332+
func TestUnmarshalEmbeddedNamed(t *testing.T) {
4333+
type Base struct {
4334+
Region string
4335+
}
4336+
type Config struct {
4337+
Base `toml:"base"`
4338+
PID uint32
4339+
}
4340+
4341+
var c Config
4342+
err := toml.Unmarshal([]byte("PID = 42\n[base]\nRegion = 'us'\n"), &c)
4343+
assert.NoError(t, err)
4344+
assert.Equal(t, Config{Base: Base{Region: "us"}, PID: 42}, c)
4345+
4346+
// Promoted keys must NOT populate a named embedded struct.
4347+
var c2 Config
4348+
err = toml.Unmarshal([]byte("Region = 'us'\nPID = 42\n"), &c2)
4349+
assert.NoError(t, err)
4350+
assert.Equal(t, Config{PID: 42}, c2)
4351+
}
4352+
42994353
func TestUnmarshal_Nil(t *testing.T) {
43004354
type Foo struct {
43014355
Foo *Foo `toml:"foo,omitempty"`

0 commit comments

Comments
 (0)