reflect: add FieldByName(), and FieldByIndex()

Этот коммит содержится в:
Damian Gryski 2023-03-01 11:33:39 -08:00 коммит произвёл Ayke
родитель 9f02340a26
коммит fa4f361ca7
3 изменённых файлов: 184 добавлений и 45 удалений

Просмотреть файл

@ -575,6 +575,40 @@ func (t *rawType) Field(i int) StructField {
}
}
func rawStructFieldFromPointer(fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uintptr) rawStructField {
// Read the field tag, if there is one.
var tag string
if flagsByte&structFieldFlagHasTag != 0 {
data = unsafe.Add(data, 1) // C: data+1
tagLen := uintptr(*(*byte)(data))
data = unsafe.Add(data, 1) // C: data+1
tag = *(*string)(unsafe.Pointer(&stringHeader{
data: data,
len: tagLen,
}))
}
// Set the PkgPath to some (arbitrary) value if the package path is not
// exported.
pkgPath := ""
if flagsByte&structFieldFlagIsExported == 0 {
// This field is unexported.
// TODO: list the real package path here. Storing it should not
// significantly impact binary size as there is only a limited
// number of packages in any program.
pkgPath = "<unimplemented>"
}
return rawStructField{
Name: name,
PkgPath: pkgPath,
Type: fieldType,
Tag: StructTag(tag),
Anonymous: flagsByte&structFieldFlagAnonymous != 0,
Offset: offset,
}
}
// rawField returns nearly the same value as Field but without converting the
// Type member to an interface.
//
@ -611,49 +645,56 @@ func (t *rawType) rawField(n int) rawStructField {
flagsByte := *(*byte)(data)
data = unsafe.Add(data, 1)
// Read the field name.
nameStart := data
var nameLen uintptr
for *(*byte)(data) != 0 {
nameLen++
data = unsafe.Add(data, 1) // C: data++
}
name := *(*string)(unsafe.Pointer(&stringHeader{
data: nameStart,
len: nameLen,
}))
name := readStringZ(data)
data = unsafe.Add(data, len(name))
// Read the field tag, if there is one.
var tag string
if flagsByte&structFieldFlagHasTag != 0 {
data = unsafe.Add(data, 1) // C: data+1
tagLen := uintptr(*(*byte)(data))
data = unsafe.Add(data, 1) // C: data+1
tag = *(*string)(unsafe.Pointer(&stringHeader{
data: data,
len: tagLen,
}))
return rawStructFieldFromPointer(field.fieldType, data, flagsByte, name, offset)
}
// rawFieldByName returns nearly the same value as FieldByName but without converting the
// Type member to an interface.
//
// For internal use only.
func (t *rawType) rawFieldByName(n string) (rawStructField, int, bool) {
if t.Kind() != Struct {
panic(&TypeError{"Field"})
}
descriptor := (*structType)(unsafe.Pointer(t.underlying()))
// Iterate over all the fields looking for the matching name
// Also calculate field offset.
field := &descriptor.fields[0]
var offset uintptr = 0
for i := uint16(0); i < descriptor.numField; i++ {
data := field.data
// Read some flags of this field, like whether the field is an embedded
// field. See structFieldFlagAnonymous and similar flags.
flagsByte := *(*byte)(data)
data = unsafe.Add(data, 1)
name := readStringZ(data)
data = unsafe.Add(data, len(name))
if name == n {
return rawStructFieldFromPointer(field.fieldType, data, flagsByte, name, offset), int(i), true
}
// update offset/field pointer if there *is* a next field
if i < descriptor.numField-1 {
offset += field.fieldType.Size()
// Increment pointer to the next field.
field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{})))
// Align the offset for the next field.
offset = align(offset, uintptr(field.fieldType.Align()))
}
}
// Set the PkgPath to some (arbitrary) value if the package path is not
// exported.
pkgPath := ""
if flagsByte&structFieldFlagIsExported == 0 {
// This field is unexported.
// TODO: list the real package path here. Storing it should not
// significantly impact binary size as there is only a limited
// number of packages in any program.
pkgPath = "<unimplemented>"
}
// No match
return rawStructField{}, 0, false
return rawStructField{
Name: name,
PkgPath: pkgPath,
Type: field.fieldType,
Tag: StructTag(tag),
Anonymous: flagsByte&structFieldFlagAnonymous != 0,
Offset: offset,
}
}
// Bits returns the number of bits that this type uses. It is only valid for
@ -959,12 +1000,49 @@ func (t *rawType) PkgPath() string {
return ""
}
func (t rawType) FieldByName(name string) (StructField, bool) {
panic("unimplemented: (reflect.Type).FieldByName()")
func (t *rawType) FieldByName(name string) (StructField, bool) {
if t.Kind() != Struct {
panic(TypeError{"FieldByName"})
}
field, index, ok := t.rawFieldByName(name)
if !ok {
return StructField{}, false
}
return StructField{
Name: field.Name,
PkgPath: field.PkgPath,
Type: field.Type, // note: converts rawType to Type
Tag: field.Tag,
Anonymous: field.Anonymous,
Offset: field.Offset,
Index: []int{index},
}, true
}
func (t rawType) FieldByIndex(index []int) StructField {
panic("unimplemented: (reflect.Type).FieldByIndex()")
func (t *rawType) FieldByIndex(index []int) StructField {
ftype := t
var field rawStructField
for _, n := range index {
if ftype.Kind() != Struct {
panic(TypeError{"FieldByIndex"})
}
field = ftype.rawField(n)
ftype = field.Type
}
return StructField{
Name: field.Name,
PkgPath: field.PkgPath,
Type: field.Type, // note: converts rawType to Type
Tag: field.Tag,
Anonymous: field.Anonymous,
Offset: field.Offset,
Index: index,
}
}
// A StructField describes a single field in a struct.

Просмотреть файл

@ -1400,7 +1400,24 @@ func (v Value) SetMapIndex(key, elem Value) {
// FieldByIndex returns the nested field corresponding to index.
func (v Value) FieldByIndex(index []int) Value {
panic("unimplemented: (reflect.Value).FieldByIndex()")
if len(index) == 1 {
return v.Field(index[0])
}
if v.Kind() != Struct {
panic(&ValueError{"FieldByIndex", v.Kind()})
}
for i, x := range index {
if i > 0 {
if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct {
if v.IsNil() {
panic("reflect: indirection through nil pointer to embedded struct")
}
v = v.Elem()
}
}
v = v.Field(x)
}
return v
}
// FieldByIndexErr returns the nested field corresponding to index.
@ -1409,7 +1426,14 @@ func (v Value) FieldByIndexErr(index []int) (Value, error) {
}
func (v Value) FieldByName(name string) Value {
panic("unimplemented: (reflect.Value).FieldByName()")
if v.Kind() != Struct {
panic(&ValueError{"FieldByName", v.Kind()})
}
if field, ok := v.typecode.FieldByName(name); ok {
return v.FieldByIndex(field.Index)
}
return Value{}
}
//go:linkname hashmapMake runtime.hashmapMakeUnsafePointer

Просмотреть файл

@ -266,6 +266,43 @@ func TestNamedTypes(t *testing.T) {
}
}
func TestStruct(t *testing.T) {
type barStruct struct {
QuxString string
BazInt int
}
type foobar struct {
Foo string `foo:"struct tag"`
Bar barStruct
}
var fb foobar
fb.Bar.QuxString = "qux"
reffb := TypeOf(fb)
q := reffb.FieldByIndex([]int{1, 0})
if want := "QuxString"; q.Name != want {
t.Errorf("FieldByIndex=%v, want %v", q.Name, want)
}
var ok bool
q, ok = reffb.FieldByName("Foo")
if q.Name != "Foo" || !ok {
t.Errorf("FieldByName(Foo)=%v,%v, want Foo, true")
}
if got, want := q.Tag, `foo:"struct tag"`; string(got) != want {
t.Errorf("StrucTag for Foo=%v, want %v", got, want)
}
q, ok = reffb.FieldByName("Snorble")
if q.Name != "" || ok {
t.Errorf("FieldByName(Snorble)=%v,%v, want ``, false")
}
}
func TestZero(t *testing.T) {
s := "hello, world"
var sptr *string = &s