Repository: BurntSushi/toml (go)

Issue:
panic when marshalling a map with the key in a custom string type 
### Env
- Go version: 1.22.3
- BurntSushi/toml@v1.4.0

### Description
When marshalling a map with keys in a custom string type like:
```go
type CustomString string
type MyMap map[CustomString]int

func main() {
	var myKey CustomString = "MyKey"
	m := MyMap{myKey: 10}
	toml.NewEncoder(os.Stdout).Encode(m)
}
```

it turns out that the encoder simply uses a primitive string to set the map key instead of the `CustomString` type here, and causes panic.
As both `encoding/json` and `goyaml` can handle this case without any problem, I expect this toml package should also work in the same way? 
If not, is there any way to do this properly?

Thanks

#### Stacktrace

```
panic: reflect.Value.MapIndex: value of type string is not assignable to type main.CustomString [recovered]
	panic: reflect.Value.MapIndex: value of type string is not assignable to type main.CustomString

goroutine 1 [running]:
github.com/BurntSushi/toml.(*Encoder).safeEncode.func1()
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:160 +0x73
panic({0xfb7b680?, 0xc0000140d0?})
	/usr/local/Cellar/go/1.22.3/libexec/src/runtime/panic.go:770 +0x132
reflect.Value.assignTo({0xfb7b680?, 0xc0000140c0?, 0xfc1ba80?}, {0xfb4ecf7, 0x16}, 0xfb7b640, 0x0)
	/usr/local/Cellar/go/1.22.3/libexec/src/reflect/value.go:3356 +0x299
reflect.Value.MapIndex({0xfb80360?, 0xc0000760c0?, 0x0?}, {0xfb7b680, 0xc0000140c0, 0x98})
	/usr/local/Cellar/go/1.22.3/libexec/src/reflect/value.go:1821 +0xe8
github.com/BurntSushi/toml.(*Encoder).eMap.func1({0xc0000140b0, 0x1, 0x2?}, 0x0)
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:418 +0x154
github.com/BurntSushi/toml.(*Encoder).eMap(0xc00010df20, {0xfc7a980, 0x0, 0x0}, {0xfb80360?, 0xc0000760c0?, 0xfb712f0?}, 0x0)
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:437 +0x42c
github.com/BurntSushi/toml.(*Encoder).eMapOrStruct(0xfb06f32?, {0xfc7a980?, 0xfacba9b?, 0xc00010dce0?}, {0xfb80360?, 0xc0000760c0?, 0xfb80360?}, 0x20?)
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:388 +0x4c
github.com/BurntSushi/toml.(*Encoder).eTable(0xc00010df20, {0xfc7a980, 0x0, 0x0}, {0xfb80360?, 0xc0000760c0?, 0x0?})
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:382 +0x19c
github.com/BurntSushi/toml.(*Encoder).encode(0xc00010df20, {0xfc7a980, 0x0, 0x0}, {0xfb80360?, 0xc0000760c0?, 0xc000100680?})
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:203 +0x1b5
github.com/BurntSushi/toml.(*Encoder).safeEncode(0xfb80360?, {0xfc7a980?, 0x0?, 0xfc1c100?}, {0xfb80360?, 0xc0000760c0?, 0x40?})
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:163 +0x6c
github.com/BurntSushi/toml.(*Encoder).Encode(0xc00010df20, {0xfb80360?, 0xc0000760c0?})
	/go/pkg/mod/github.com/!burnt!sushi/toml@v1.4.0/encode.go:146 +0x9c

main.main()
	/myrepo/mygo/cmd/mytest/main.go:16 +0x10f
exit status 2
```


Pull request:
Allow custom string types as map keys
Because the map keys were converted to []string,
rv.MapIndex(reflect.ValueOf(mapKey)) would panic as it's the "wrong" type (string, instead of the custom string type).

Just keep track of the reflect.Value instead; don't really need to convert it to strings.

Fixes #413

Patch:
diff --git a/encode.go b/encode.go
index 73366c0..bde6535 100644
--- a/encode.go
+++ b/encode.go
@@ -402,31 +402,30 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
 
 	// Sort keys so that we have deterministic output. And write keys directly
 	// underneath this key first, before writing sub-structs or sub-maps.
-	var mapKeysDirect, mapKeysSub []string
+	var mapKeysDirect, mapKeysSub []reflect.Value
 	for _, mapKey := range rv.MapKeys() {
-		k := mapKey.String()
 		if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) {
-			mapKeysSub = append(mapKeysSub, k)
+			mapKeysSub = append(mapKeysSub, mapKey)
 		} else {
-			mapKeysDirect = append(mapKeysDirect, k)
+			mapKeysDirect = append(mapKeysDirect, mapKey)
 		}
 	}
 
-	var writeMapKeys = func(mapKeys []string, trailC bool) {
-		sort.Strings(mapKeys)
+	writeMapKeys := func(mapKeys []reflect.Value, trailC bool) {
+		sort.Slice(mapKeys, func(i, j int) bool { return mapKeys[i].String() < mapKeys[j].String() })
 		for i, mapKey := range mapKeys {
-			val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey)))
+			val := eindirect(rv.MapIndex(mapKey))
 			if isNil(val) {
 				continue
 			}
 
 			if inline {
-				enc.writeKeyValue(Key{mapKey}, val, true)
+				enc.writeKeyValue(Key{mapKey.String()}, val, true)
 				if trailC || i != len(mapKeys)-1 {
 					enc.wf(", ")
 				}
 			} else {
-				enc.encode(key.add(mapKey), val)
+				enc.encode(key.add(mapKey.String()), val)
 			}
 		}
 	}


Test patch:
diff --git a/encode_test.go b/encode_test.go
index fbf17de..a5c3a7e 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -837,10 +837,13 @@ func TestEncodeJSONNumber(t *testing.T) {
 }
 
 func TestEncode(t *testing.T) {
-	type Embedded struct {
-		Int int `toml:"_int"`
-	}
-	type NonStruct int
+	type (
+		Embedded struct {
+			Int int `toml:"_int"`
+		}
+		NonStruct int
+		MyInt     int
+	)
 
 	date := time.Date(2014, 5, 11, 19, 30, 40, 0, time.UTC)
 	dateStr := "2014-05-11T19:30:40Z"
@@ -1165,6 +1168,10 @@ ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
 			input:     map[int]string{1: ""},
 			wantError: errNonString,
 		},
+		"(error) map no string key indirect": {
+			input:     map[MyInt]string{1: ""},
+			wantError: errNonString,
+		},
 
 		"tbl-in-arr-struct": {
 			input: struct {
@@ -1279,7 +1286,7 @@ func encodeExpected(t *testing.T, label string, val any, want string, wantErr er
 				if wantErr == errAnything && err != nil {
 					return
 				}
-				t.Errorf("want Encode error %v, got %v", wantErr, err)
+				t.Errorf("wrong error:\nwant: %v\nhave: %v", wantErr, err)
 			} else {
 				t.Errorf("Encode failed: %s", err)
 			}
@@ -1297,3 +1304,40 @@ func encodeExpected(t *testing.T, label string, val any, want string, wantErr er
 		}
 	})
 }
+
+func TestMapCustomKeytype(t *testing.T) {
+	type (
+		MyString string
+		MyMap    map[MyString]any
+	)
+
+	m := MyMap{
+		"k1":     "a",
+		"nested": MyMap{"k2": "b"},
+	}
+	have := new(bytes.Buffer)
+	err := NewEncoder(have).Encode(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := `
+k1 = "a"
+
+[nested]
+  k2 = "b"
+`[1:]
+
+	if have.String() != want {
+		t.Fatalf("\nhave: %q\nwant: %q", have, want)
+	}
+
+	var m2 MyMap
+	_, err = Decode(have.String(), &m2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if h, w := fmt.Sprintf("%s", m2), fmt.Sprintf("%s", m); h != w {
+		t.Fatalf("\nhave: %s\nwant: %s", h, w)
+	}
+}