Repository: goccy/go-yaml
Pull request #605 (2024-12-22 14:16:57)

Fix encoding of anchor and alias
resolve https://github.com/goccy/go-yaml/issues/259
There is no longer a need to specify an alias with a tag, except when used in combination with inline.

codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/goccy/go-yaml/pull/605?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
Attention: Patch coverage is `70.00000%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 76.97%. Comparing base [(`beec790`)](https://app.codecov.io/gh/goccy/go-yaml/commit/beec790be211bb1cb661660c33a7583b834f7619?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`a47e834`)](https://app.codecov.io/gh/goccy/go-yaml/commit/a47e83455bcb0349306a65e319c610ac283b4fa1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima).

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #605      +/-   ##
==========================================
- Coverage   77.14%   76.97%   -0.18%     
==========================================
  Files          22       22              
  Lines        7886     7891       +5     
==========================================
- Hits         6084     6074      -10     
- Misses       1382     1400      +18     
+ Partials      420      417       -3     
```

</details>

Patch:
diff --git a/README.md b/README.md
index 820357d..b8c2c53 100644
--- a/README.md
+++ b/README.md
@@ -186,7 +186,9 @@ fmt.Printf("%+v\n", v) // {A:{B:1 C:hello}}
 
 ### 3.1. Explicitly declared `Anchor` name and `Alias` name
 
-If you want to use `anchor` or `alias`, you can define it as a struct tag.
+If you want to use `anchor`, you can define it as a struct tag.
+If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
+If an explicit alias name is specified, an error is raised if its value is different from the value specified in the anchor.
 
 ```go
 type T struct {
@@ -216,10 +218,7 @@ d: *x
 
 If you do not explicitly declare the anchor name, the default behavior is to
 use the equivalent of `strings.ToLower($FieldName)` as the name of the anchor.
-
-If you do not explicitly declare the alias name AND the value is a pointer
-to another element, we look up the anchor name by finding out which anchor
-field the value is assigned to by looking up its pointer address.
+If the value specified for an anchor is a pointer type and the same address as the pointer is found, the value is automatically set to alias.
 
 ```go
 type T struct {
@@ -229,8 +228,8 @@ type T struct {
 var v struct {
 	A *T `yaml:"a,anchor"`
 	B *T `yaml:"b,anchor"`
-	C *T `yaml:"c,alias"`
-	D *T `yaml:"d,alias"`
+	C *T `yaml:"c"`
+	D *T `yaml:"d"`
 }
 v.A = &T{I: 1, S: "hello"}
 v.B = &T{I: 2, S: "world"}
diff --git a/encode.go b/encode.go
index 265d044..027b1be 100644
--- a/encode.go
+++ b/encode.go
@@ -787,42 +787,27 @@ func (e *Encoder) encodeStruct(ctx context.Context, value reflect.Value, column
 		}
 		var key ast.MapKeyNode = e.encodeString(structField.RenderName, column)
 		switch {
-		case structField.AnchorName != "":
-			anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column)
-			if err != nil {
-				return nil, err
-			}
-			value = anchorNode
-		case structField.IsAutoAlias:
-			if fieldValue.Kind() != reflect.Ptr {
-				return nil, fmt.Errorf(
-					"%s in struct is not pointer type. but required automatically alias detection",
-					structField.FieldName,
-				)
-			}
-			anchorName := e.anchorPtrToNameMap[fieldValue.Pointer()]
-			if anchorName == "" {
-				return nil, errors.New(
-					"cannot find anchor name from pointer address for automatically alias detection",
-				)
+		case value.Type() == ast.AliasType:
+			if aliasName := structField.AliasName; aliasName != "" {
+				alias, ok := value.(*ast.AliasNode)
+				if !ok {
+					return nil, errors.ErrUnexpectedNodeType(value.Type(), ast.AliasType, value.GetToken())
+				}
+				got := alias.Value.String()
+				if aliasName != got {
+					return nil, fmt.Errorf("expected alias name is %q but got %q", aliasName, got)
+				}
 			}
-			aliasName := anchorName
-			alias := ast.Alias(token.New("*", "*", e.pos(column)))
-			alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
-			value = alias
 			if structField.IsInline {
 				// if both used alias and inline, output `<<: *alias`
 				key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
 			}
-		case structField.AliasName != "":
-			aliasName := structField.AliasName
-			alias := ast.Alias(token.New("*", "*", e.pos(column)))
-			alias.Value = ast.String(token.New(aliasName, aliasName, e.pos(column)))
-			value = alias
-			if structField.IsInline {
-				// if both used alias and inline, output `<<: *alias`
-				key = ast.MergeKey(token.New("<<", "<<", e.pos(column)))
+		case structField.AnchorName != "":
+			anchorNode, err := e.encodeAnchor(structField.AnchorName, value, fieldValue, column)
+			if err != nil {
+				return nil, err
 			}
+			value = anchorNode
 		case structField.IsInline:
 			isAutoAnchor := structField.IsAutoAnchor
 			if !hasInlineAnchorField {


Test patch:
diff --git a/encode_test.go b/encode_test.go
index bcf2f3f..1d9b9bd 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -804,8 +804,8 @@ func TestEncodeWithAutoAlias(t *testing.T) {
 	var v struct {
 		A *T `yaml:"a,anchor=a"`
 		B *T `yaml:"b,anchor=b"`
-		C *T `yaml:"c,alias"`
-		D *T `yaml:"d,alias"`
+		C *T `yaml:"c"`
+		D *T `yaml:"d"`
 	}
 	v.A = &T{I: 1, S: "hello"}
 	v.B = &T{I: 2, S: "world"}
@@ -838,8 +838,8 @@ func TestEncodeWithImplicitAnchorAndAlias(t *testing.T) {
 	var v struct {
 		A *T `yaml:"a,anchor"`
 		B *T `yaml:"b,anchor"`
-		C *T `yaml:"c,alias"`
-		D *T `yaml:"d,alias"`
+		C *T `yaml:"c"`
+		D *T `yaml:"d"`
 	}
 	v.A = &T{I: 1, S: "hello"}
 	v.B = &T{I: 2, S: "world"}
@@ -864,7 +864,7 @@ d: *b
 
 func TestEncodeWithMerge(t *testing.T) {
 	type Person struct {
-		*Person `yaml:",omitempty,inline,alias"`
+		*Person `yaml:",omitempty,inline"`
 		Name    string `yaml:",omitempty"`
 		Age     int    `yaml:",omitempty"`
 	}
@@ -1151,8 +1151,8 @@ func TestEncoder_MarshalAnchor(t *testing.T) {
 		Host *Host `yaml:",anchor"`
 	}
 	type Queue struct {
-		Name  string `yaml:","`
-		*Host `yaml:",alias"`
+		Name string `yaml:","`
+		*Host
 	}
 	var doc struct {
 		Hosts  []*HostDecl `yaml:"hosts"`
@@ -1338,8 +1338,8 @@ func ExampleMarshal_implicitAnchorAlias() {
 	var v struct {
 		A *T `yaml:"a,anchor"`
 		B *T `yaml:"b,anchor"`
-		C *T `yaml:"c,alias"`
-		D *T `yaml:"d,alias"`
+		C *T `yaml:"c"`
+		D *T `yaml:"d"`
 	}
 	v.A = &T{I: 1, S: "hello"}
 	v.B = &T{I: 2, S: "world"}
@@ -1675,3 +1675,45 @@ func TestIssue174(t *testing.T) {
 		t.Fatalf("failed to encode: %q", got)
 	}
 }
+
+func TestIssue259(t *testing.T) {
+	type AnchorValue struct {
+		Foo uint64
+		Bar string
+	}
+
+	type Value struct {
+		Baz   string       `yaml:"baz"`
+		Value *AnchorValue `yaml:"value,anchor"`
+	}
+
+	type Schema struct {
+		Values []*Value
+	}
+
+	schema := Schema{}
+	anchorValue := AnchorValue{Foo: 3, Bar: "bar"}
+	schema.Values = []*Value{
+		{Baz: "xxx", Value: &anchorValue},
+		{Baz: "yyy", Value: &anchorValue},
+		{Baz: "zzz", Value: &anchorValue},
+	}
+	b, err := yaml.Marshal(schema)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := `
+values:
+- baz: xxx
+  value: &value
+    foo: 3
+    bar: bar
+- baz: yyy
+  value: *value
+- baz: zzz
+  value: *value
+`
+	if strings.TrimPrefix(expected, "\n") != string(b) {
+		t.Fatalf("failed to encode: got = %s", string(b))
+	}
+}
diff --git a/testdata/yaml_test.go b/testdata/yaml_test.go
index d3ca816..55a3c91 100644
--- a/testdata/yaml_test.go
+++ b/testdata/yaml_test.go
@@ -312,7 +312,7 @@ b: *a
 `
 	var v struct {
 		A *int `yaml:"a,anchor"`
-		B *int `yaml:"b,alias"`
+		B *int `yaml:"b"`
 	}
 	if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
 		t.Fatal(err)