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 // rawField returns nearly the same value as Field but without converting the
// Type member to an interface. // Type member to an interface.
// //
@ -611,49 +645,56 @@ func (t *rawType) rawField(n int) rawStructField {
flagsByte := *(*byte)(data) flagsByte := *(*byte)(data)
data = unsafe.Add(data, 1) data = unsafe.Add(data, 1)
// Read the field name. name := readStringZ(data)
nameStart := data data = unsafe.Add(data, len(name))
var nameLen uintptr
for *(*byte)(data) != 0 {
nameLen++
data = unsafe.Add(data, 1) // C: data++
}
name := *(*string)(unsafe.Pointer(&stringHeader{
data: nameStart,
len: nameLen,
}))
// Read the field tag, if there is one. return rawStructFieldFromPointer(field.fieldType, data, flagsByte, name, offset)
var tag string }
if flagsByte&structFieldFlagHasTag != 0 {
data = unsafe.Add(data, 1) // C: data+1 // rawFieldByName returns nearly the same value as FieldByName but without converting the
tagLen := uintptr(*(*byte)(data)) // Type member to an interface.
data = unsafe.Add(data, 1) // C: data+1 //
tag = *(*string)(unsafe.Pointer(&stringHeader{ // For internal use only.
data: data, func (t *rawType) rawFieldByName(n string) (rawStructField, int, bool) {
len: tagLen, 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
} }
// Set the PkgPath to some (arbitrary) value if the package path is not // update offset/field pointer if there *is* a next field
// exported. if i < descriptor.numField-1 {
pkgPath := "" offset += field.fieldType.Size()
if flagsByte&structFieldFlagIsExported == 0 {
// This field is unexported. // Increment pointer to the next field.
// TODO: list the real package path here. Storing it should not field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{})))
// significantly impact binary size as there is only a limited
// number of packages in any program. // Align the offset for the next field.
pkgPath = "<unimplemented>" offset = align(offset, uintptr(field.fieldType.Align()))
}
} }
return rawStructField{ // No match
Name: name, return rawStructField{}, 0, false
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 // 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 "" return ""
} }
func (t rawType) FieldByName(name string) (StructField, bool) { func (t *rawType) FieldByName(name string) (StructField, bool) {
panic("unimplemented: (reflect.Type).FieldByName()") 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 { func (t *rawType) FieldByIndex(index []int) StructField {
panic("unimplemented: (reflect.Type).FieldByIndex()") 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. // 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. // FieldByIndex returns the nested field corresponding to index.
func (v Value) FieldByIndex(index []int) Value { 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. // 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 { 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 //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) { func TestZero(t *testing.T) {
s := "hello, world" s := "hello, world"
var sptr *string = &s var sptr *string = &s