reflect: implement support for array types
Этот коммит содержится в:
родитель
bbc3046687
коммит
c19c738f52
8 изменённых файлов: 184 добавлений и 20 удалений
|
@ -54,6 +54,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||||
// element types. Store it directly in the typecode global to make
|
// element types. Store it directly in the typecode global to make
|
||||||
// reflect lowering simpler.
|
// reflect lowering simpler.
|
||||||
var references llvm.Value
|
var references llvm.Value
|
||||||
|
var length int64
|
||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
references = c.getTypeCode(typ.Underlying())
|
references = c.getTypeCode(typ.Underlying())
|
||||||
|
@ -63,6 +64,9 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||||
references = c.getTypeCode(typ.Elem())
|
references = c.getTypeCode(typ.Elem())
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
references = c.getTypeCode(typ.Elem())
|
references = c.getTypeCode(typ.Elem())
|
||||||
|
case *types.Array:
|
||||||
|
references = c.getTypeCode(typ.Elem())
|
||||||
|
length = typ.Len()
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
// Take a pointer to the typecodeID of the first field (if it exists).
|
||||||
structGlobal := c.makeStructTypeFields(typ)
|
structGlobal := c.makeStructTypeFields(typ)
|
||||||
|
@ -72,6 +76,10 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||||
// Set the 'references' field of the runtime.typecodeID struct.
|
// Set the 'references' field of the runtime.typecodeID struct.
|
||||||
globalValue := c.getZeroValue(global.Type().ElementType())
|
globalValue := c.getZeroValue(global.Type().ElementType())
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
||||||
|
if length != 0 {
|
||||||
|
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||||
|
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||||
|
}
|
||||||
global.SetInitializer(globalValue)
|
global.SetInitializer(globalValue)
|
||||||
global.SetLinkage(llvm.PrivateLinkage)
|
global.SetLinkage(llvm.PrivateLinkage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,11 @@ type typeCodeAssignmentState struct {
|
||||||
namedBasicTypes map[string]int
|
namedBasicTypes map[string]int
|
||||||
namedNonBasicTypes map[string]int
|
namedNonBasicTypes map[string]int
|
||||||
|
|
||||||
|
// Map of array types to their type code.
|
||||||
|
arrayTypes map[string]int
|
||||||
|
arrayTypesSidetable []byte
|
||||||
|
needsArrayTypesSidetable bool
|
||||||
|
|
||||||
// Map of struct types to their type code.
|
// Map of struct types to their type code.
|
||||||
structTypes map[string]int
|
structTypes map[string]int
|
||||||
structTypesSidetable []byte
|
structTypesSidetable []byte
|
||||||
|
@ -137,11 +142,13 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||||
uintptrLen: c.uintptrType.IntTypeWidth(),
|
uintptrLen: c.uintptrType.IntTypeWidth(),
|
||||||
namedBasicTypes: make(map[string]int),
|
namedBasicTypes: make(map[string]int),
|
||||||
namedNonBasicTypes: make(map[string]int),
|
namedNonBasicTypes: make(map[string]int),
|
||||||
|
arrayTypes: make(map[string]int),
|
||||||
structTypes: make(map[string]int),
|
structTypes: make(map[string]int),
|
||||||
structNames: make(map[string]int),
|
structNames: make(map[string]int),
|
||||||
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
|
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
|
||||||
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
|
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
|
||||||
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
||||||
|
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
||||||
}
|
}
|
||||||
for _, t := range typeSlice {
|
for _, t := range typeSlice {
|
||||||
num := state.getTypeCodeNum(t.typecode)
|
num := state.getTypeCodeNum(t.typecode)
|
||||||
|
@ -161,6 +168,11 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
global.SetUnnamedAddr(true)
|
global.SetUnnamedAddr(true)
|
||||||
}
|
}
|
||||||
|
if state.needsArrayTypesSidetable {
|
||||||
|
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
|
||||||
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
|
global.SetUnnamedAddr(true)
|
||||||
|
}
|
||||||
if state.needsStructTypesSidetable {
|
if state.needsStructTypesSidetable {
|
||||||
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
|
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
|
@ -268,6 +280,10 @@ func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode
|
||||||
// Prefix-style type kinds. The upper bits contain the element type.
|
// Prefix-style type kinds. The upper bits contain the element type.
|
||||||
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||||
return state.getTypeCodeNum(sub)
|
return state.getTypeCodeNum(sub)
|
||||||
|
case "array":
|
||||||
|
// An array is basically a pair of (typecode, length) stored in a
|
||||||
|
// sidetable.
|
||||||
|
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
|
||||||
case "struct":
|
case "struct":
|
||||||
// More complicated type kind. The upper bits contain the index to the
|
// More complicated type kind. The upper bits contain the index to the
|
||||||
// struct type in the struct types sidetable.
|
// struct type in the struct types sidetable.
|
||||||
|
@ -312,6 +328,43 @@ func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
|
||||||
return num
|
return num
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getArrayTypeNum returns the array type number, which is an index into the
|
||||||
|
// reflect.arrayTypesSidetable or a unique number for this type if this table is
|
||||||
|
// not used.
|
||||||
|
func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int {
|
||||||
|
name := typecode.Name()
|
||||||
|
if num, ok := state.arrayTypes[name]; ok {
|
||||||
|
// This array type already has an entry in the sidetable. Don't store
|
||||||
|
// it twice.
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.needsArrayTypesSidetable {
|
||||||
|
// We don't need array sidetables, so we can just assign monotonically
|
||||||
|
// increasing numbers to each array type.
|
||||||
|
num := len(state.arrayTypes)
|
||||||
|
state.arrayTypes[name] = num
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
elemTypeCode := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||||
|
elemTypeNum := state.getTypeCodeNum(elemTypeCode)
|
||||||
|
if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() {
|
||||||
|
// TODO: make this a regular error
|
||||||
|
panic("array element type has a type code that is too big")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The array side table is a sequence of {element type, array length}.
|
||||||
|
arrayLength := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1}).ZExtValue()
|
||||||
|
buf := makeVarint(elemTypeNum.Uint64())
|
||||||
|
buf = append(buf, makeVarint(arrayLength)...)
|
||||||
|
|
||||||
|
index := len(state.arrayTypesSidetable)
|
||||||
|
state.arrayTypes[name] = index
|
||||||
|
state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
// getStructTypeNum returns the struct type number, which is an index into
|
// getStructTypeNum returns the struct type number, which is an index into
|
||||||
// reflect.structTypesSidetable or an unique number for every struct if this
|
// reflect.structTypesSidetable or an unique number for every struct if this
|
||||||
// sidetable is not needed in the to-be-compiled program.
|
// sidetable is not needed in the to-be-compiled program.
|
||||||
|
|
|
@ -16,6 +16,9 @@ var structTypesSidetable byte
|
||||||
//go:extern reflect.structNamesSidetable
|
//go:extern reflect.structNamesSidetable
|
||||||
var structNamesSidetable byte
|
var structNamesSidetable byte
|
||||||
|
|
||||||
|
//go:extern reflect.arrayTypesSidetable
|
||||||
|
var arrayTypesSidetable byte
|
||||||
|
|
||||||
// readStringSidetable reads a string from the given table (like
|
// readStringSidetable reads a string from the given table (like
|
||||||
// structNamesSidetable) and returns this string. No heap allocation is
|
// structNamesSidetable) and returns this string. No heap allocation is
|
||||||
// necessary because it makes the string point directly to the raw bytes of the
|
// necessary because it makes the string point directly to the raw bytes of the
|
||||||
|
|
|
@ -142,11 +142,17 @@ func (t Type) Kind() Kind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Elem returns the element type for channel, slice and array types, the
|
||||||
|
// pointed-to value for pointer types, and the key type for map types.
|
||||||
func (t Type) Elem() Type {
|
func (t Type) Elem() Type {
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case Chan, Ptr, Slice:
|
case Chan, Ptr, Slice:
|
||||||
return t.stripPrefix()
|
return t.stripPrefix()
|
||||||
default: // not implemented: Array, Map
|
case Array:
|
||||||
|
index := t.stripPrefix()
|
||||||
|
elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index)))
|
||||||
|
return Type(elem)
|
||||||
|
default: // not implemented: Map
|
||||||
panic("unimplemented: (reflect.Type).Elem()")
|
panic("unimplemented: (reflect.Type).Elem()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,8 +260,20 @@ func (t Type) Bits() int {
|
||||||
panic(TypeError{"Bits"})
|
panic(TypeError{"Bits"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in this array. It panics of the type kind
|
||||||
|
// is not Array.
|
||||||
func (t Type) Len() int {
|
func (t Type) Len() int {
|
||||||
panic("unimplemented: (reflect.Type).Len()")
|
if t.Kind() != Array {
|
||||||
|
panic(TypeError{"Len"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip past the element type
|
||||||
|
arrayIdentifier := t.stripPrefix()
|
||||||
|
_, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier)))
|
||||||
|
|
||||||
|
// Read the array length.
|
||||||
|
arrayLen, _ := readVarint(p)
|
||||||
|
return int(arrayLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumField returns the number of fields of a struct type. It panics for other
|
// NumField returns the number of fields of a struct type. It panics for other
|
||||||
|
@ -301,6 +319,8 @@ func (t Type) Size() uintptr {
|
||||||
return unsafe.Sizeof(SliceHeader{})
|
return unsafe.Sizeof(SliceHeader{})
|
||||||
case Interface:
|
case Interface:
|
||||||
return unsafe.Sizeof(interfaceHeader{})
|
return unsafe.Sizeof(interfaceHeader{})
|
||||||
|
case Array:
|
||||||
|
return t.Elem().Size() * uintptr(t.Len())
|
||||||
case Struct:
|
case Struct:
|
||||||
numField := t.NumField()
|
numField := t.NumField()
|
||||||
if numField == 0 {
|
if numField == 0 {
|
||||||
|
|
|
@ -302,6 +302,8 @@ func (v Value) Slice(i, j int) Value {
|
||||||
panic("unimplemented: (reflect.Value).Slice()")
|
panic("unimplemented: (reflect.Value).Slice()")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Len returns the length of this value for slices, strings, arrays, channels,
|
||||||
|
// and maps. For oter types, it panics.
|
||||||
func (v Value) Len() int {
|
func (v Value) Len() int {
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
|
@ -309,7 +311,9 @@ func (v Value) Len() int {
|
||||||
return int((*SliceHeader)(v.value).Len)
|
return int((*SliceHeader)(v.value).Len)
|
||||||
case String:
|
case String:
|
||||||
return int((*StringHeader)(v.value).Len)
|
return int((*StringHeader)(v.value).Len)
|
||||||
default: // Array, Chan, Map
|
case Array:
|
||||||
|
return v.Type().Len()
|
||||||
|
default: // Chan, Map
|
||||||
panic("unimplemented: (reflect.Value).Len()")
|
panic("unimplemented: (reflect.Value).Len()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,27 +396,21 @@ func (v Value) Field(i int) Value {
|
||||||
// afterwards, so load the value (from the correct offset) and return
|
// afterwards, so load the value (from the correct offset) and return
|
||||||
// it.
|
// it.
|
||||||
ptr := unsafe.Pointer(uintptr(v.value) + structField.Offset)
|
ptr := unsafe.Pointer(uintptr(v.value) + structField.Offset)
|
||||||
loadedValue := uintptr(0)
|
value := unsafe.Pointer(loadValue(ptr, fieldSize))
|
||||||
shift := uintptr(0)
|
|
||||||
for i := uintptr(0); i < fieldSize; i++ {
|
|
||||||
loadedValue |= uintptr(*(*byte)(ptr)) << shift
|
|
||||||
shift += 8
|
|
||||||
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
|
||||||
}
|
|
||||||
return Value{
|
return Value{
|
||||||
flags: 0,
|
flags: 0,
|
||||||
typecode: structField.Type,
|
typecode: structField.Type,
|
||||||
value: unsafe.Pointer(loadedValue),
|
value: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The value was already stored directly in the interface and it still
|
// The value was already stored directly in the interface and it still
|
||||||
// is. Cut out the part of the value that we need.
|
// is. Cut out the part of the value that we need.
|
||||||
mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - fieldSize) * 8)
|
value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize)
|
||||||
return Value{
|
return Value{
|
||||||
flags: flags,
|
flags: flags,
|
||||||
typecode: structField.Type,
|
typecode: structField.Type,
|
||||||
value: unsafe.Pointer((uintptr(v.value) >> (structField.Offset * 8)) & mask),
|
value: unsafe.Pointer(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,12 +442,75 @@ func (v Value) Index(i int) Value {
|
||||||
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
|
||||||
}
|
}
|
||||||
case Array:
|
case Array:
|
||||||
panic("unimplemented: (reflect.Value).Index()")
|
// Extract an element from the array.
|
||||||
|
elemType := v.Type().Elem()
|
||||||
|
elemSize := elemType.Size()
|
||||||
|
size := v.Type().Size()
|
||||||
|
if size == 0 {
|
||||||
|
// The element size is 0 and/or the length of the array is 0.
|
||||||
|
return Value{
|
||||||
|
typecode: v.Type().Elem(),
|
||||||
|
flags: v.flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if elemSize > unsafe.Sizeof(uintptr(0)) {
|
||||||
|
// The resulting value doesn't fit in a pointer so must be
|
||||||
|
// indirect. Also, because size != 0 this implies that the array
|
||||||
|
// length must be != 0, and thus that the total size is at least
|
||||||
|
// elemSize.
|
||||||
|
addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value
|
||||||
|
return Value{
|
||||||
|
typecode: v.Type().Elem(),
|
||||||
|
flags: v.flags,
|
||||||
|
value: unsafe.Pointer(addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if size > unsafe.Sizeof(uintptr(0)) {
|
||||||
|
// The element fits in a pointer, but the array does not.
|
||||||
|
// Load the value from the pointer.
|
||||||
|
addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value
|
||||||
|
return Value{
|
||||||
|
typecode: v.Type().Elem(),
|
||||||
|
flags: v.flags,
|
||||||
|
value: unsafe.Pointer(loadValue(unsafe.Pointer(addr), elemSize)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The value fits in a pointer, so extract it with some shifting and
|
||||||
|
// masking.
|
||||||
|
offset := elemSize * uintptr(i)
|
||||||
|
value := maskAndShift(uintptr(v.value), offset, elemSize)
|
||||||
|
return Value{
|
||||||
|
typecode: v.Type().Elem(),
|
||||||
|
flags: v.flags,
|
||||||
|
value: unsafe.Pointer(value),
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
panic(&ValueError{"Index"})
|
panic(&ValueError{"Index"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadValue loads a value that may or may not be word-aligned. The number of
|
||||||
|
// bytes given in size are loaded. The biggest possible size it can load is that
|
||||||
|
// of an uintptr.
|
||||||
|
func loadValue(ptr unsafe.Pointer, size uintptr) uintptr {
|
||||||
|
loadedValue := uintptr(0)
|
||||||
|
shift := uintptr(0)
|
||||||
|
for i := uintptr(0); i < size; i++ {
|
||||||
|
loadedValue |= uintptr(*(*byte)(ptr)) << shift
|
||||||
|
shift += 8
|
||||||
|
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||||
|
}
|
||||||
|
return loadedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0.
|
||||||
|
func maskAndShift(value, offset, size uintptr) uintptr {
|
||||||
|
mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8)
|
||||||
|
return (uintptr(value) >> (offset * 8)) & mask
|
||||||
|
}
|
||||||
|
|
||||||
func (v Value) MapKeys() []Value {
|
func (v Value) MapKeys() []Value {
|
||||||
panic("unimplemented: (reflect.Value).MapKeys()")
|
panic("unimplemented: (reflect.Value).MapKeys()")
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,13 @@ type typecodeID struct {
|
||||||
// * basic types: null
|
// * basic types: null
|
||||||
// * named type: the underlying type
|
// * named type: the underlying type
|
||||||
// * interface: null
|
// * interface: null
|
||||||
// * chan/pointer/slice: the element type
|
// * chan/pointer/slice/array: the element type
|
||||||
// * struct: GEP of structField array (to typecode field)
|
// * struct: bitcast of global with structField array
|
||||||
// * array/func/map: TODO
|
// * func/map: TODO
|
||||||
references *typecodeID
|
references *typecodeID
|
||||||
|
|
||||||
|
// The array length, for array types.
|
||||||
|
length uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
// structField is used by the compiler to pass information to the interface
|
// structField is used by the compiler to pass information to the interface
|
||||||
|
|
8
testdata/reflect.go
предоставленный
8
testdata/reflect.go
предоставленный
|
@ -91,7 +91,8 @@ func main() {
|
||||||
[]complex128{1, 1.128 + 0.4i},
|
[]complex128{1, 1.128 + 0.4i},
|
||||||
myslice{5, 3, 11},
|
myslice{5, 3, 11},
|
||||||
// array
|
// array
|
||||||
[4]int{1, 2, 3, 4},
|
[3]int64{5, 8, 2},
|
||||||
|
[2]uint8{3, 5},
|
||||||
// functions
|
// functions
|
||||||
zeroFunc,
|
zeroFunc,
|
||||||
emptyFunc,
|
emptyFunc,
|
||||||
|
@ -287,7 +288,10 @@ func showValue(rv reflect.Value, indent string) {
|
||||||
case reflect.UnsafePointer:
|
case reflect.UnsafePointer:
|
||||||
println(indent+" pointer:", rv.Pointer() != 0)
|
println(indent+" pointer:", rv.Pointer() != 0)
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
println(indent + " array")
|
println(indent+" array:", rt.Len(), rt.Elem().Kind().String(), int(rt.Size()))
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
showValue(rv.Index(i), indent+" ")
|
||||||
|
}
|
||||||
case reflect.Chan:
|
case reflect.Chan:
|
||||||
println(indent+" chan:", rt.Elem().Kind().String())
|
println(indent+" chan:", rt.Elem().Kind().String())
|
||||||
println(indent+" nil:", rv.IsNil())
|
println(indent+" nil:", rv.IsNil())
|
||||||
|
|
14
testdata/reflect.txt
предоставленный
14
testdata/reflect.txt
предоставленный
|
@ -203,7 +203,19 @@ reflect type: slice
|
||||||
reflect type: uint8 settable=true
|
reflect type: uint8 settable=true
|
||||||
uint: 11
|
uint: 11
|
||||||
reflect type: array
|
reflect type: array
|
||||||
array
|
array: 3 int64 24
|
||||||
|
reflect type: int64
|
||||||
|
int: 5
|
||||||
|
reflect type: int64
|
||||||
|
int: 8
|
||||||
|
reflect type: int64
|
||||||
|
int: 2
|
||||||
|
reflect type: array
|
||||||
|
array: 2 uint8 2
|
||||||
|
reflect type: uint8
|
||||||
|
uint: 3
|
||||||
|
reflect type: uint8
|
||||||
|
uint: 5
|
||||||
reflect type: func
|
reflect type: func
|
||||||
func
|
func
|
||||||
nil: true
|
nil: true
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче