Skip to content

reflect, runtime: additional Go 1.24 support #4770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions src/reflect/hashmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package reflect

import (
"unsafe"
)

//go:linkname hash32 runtime.hash32
func hash32(ptr unsafe.Pointer, n, seed uintptr) uint32

//go:linkname hashmapStringHash runtime.hashmapStringHash
func hashmapStringHash(s string, seed uintptr) uint32

func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
f := *(*uint32)(ptr)
if f == 0x80000000 {
// convert -0 to 0 for hashing
f = 0
}
return hash32(unsafe.Pointer(&f), 4, seed)
}

func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
f := *(*uint64)(ptr)
if f == 0x8000000000000000 {
// convert -0 to 0 for hashing
f = 0
}
return hash32(unsafe.Pointer(&f), 8, seed)
}

func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
x := ValueOf(itf)
if x.RawType() == nil {
return 0 // nil interface
}

value := (*_interface)(unsafe.Pointer(&itf)).value
ptr := value
if x.RawType().Size() <= unsafe.Sizeof(uintptr(0)) {
// Value fits in pointer, so it's directly stored in the pointer.
ptr = unsafe.Pointer(&value)
}

switch x.RawType().Kind() {
case Int, Int8, Int16, Int32, Int64:
return hash32(ptr, x.RawType().Size(), seed)
case Bool, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return hash32(ptr, x.RawType().Size(), seed)
case Float32:
// It should be possible to just has the contents. However, NaN != NaN
// so if you're using lots of NaNs as map keys (you shouldn't) then hash
// time may become exponential. To fix that, it would be better to
// return a random number instead:
// https://research.swtch.com/randhash
return hashmapFloat32Hash(ptr, seed)
case Float64:
return hashmapFloat64Hash(ptr, seed)
case Complex64:
rptr, iptr := ptr, unsafe.Add(ptr, 4)
return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed)
case Complex128:
rptr, iptr := ptr, unsafe.Add(ptr, 8)
return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed)
case String:
return hashmapStringHash(x.String(), seed)
case Chan, Ptr, UnsafePointer:
// It might seem better to just return the pointer, but that won't
// result in an evenly distributed hashmap. Instead, hash the pointer
// like most other types.
return hash32(ptr, x.RawType().Size(), seed)
case Array:
var hash uint32
for i := 0; i < x.Len(); i++ {
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed)
}
return hash
case Struct:
var hash uint32
for i := 0; i < x.NumField(); i++ {
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed)
}
return hash
default:
runtimePanic("comparing un-comparable type")
return 0 // unreachable
}
}
70 changes: 70 additions & 0 deletions src/reflect/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package reflect

import "unsafe"

// Cause a runtime panic, which is (currently) always a string.
//
//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(msg string)

type _interface struct {
typecode unsafe.Pointer
value unsafe.Pointer
}

// Return true iff both interfaces are equal.
func interfaceEqual(x, y interface{}) bool {
return reflectValueEqual(ValueOf(x), ValueOf(y))
}

func reflectValueEqual(x, y Value) bool {
// Note: doing a x.Type() == y.Type() comparison would not work here as that
// would introduce an infinite recursion: comparing two Type values
// is done with this reflectValueEqual runtime call.
if x.RawType() == nil || y.RawType() == nil {
// One of them is nil.
return x.RawType() == y.RawType()
}

if x.RawType() != y.RawType() {
// The type is not the same, which means the interfaces are definitely
// not the same.
return false
}

switch x.RawType().Kind() {
case Bool:
return x.Bool() == y.Bool()
case Int, Int8, Int16, Int32, Int64:
return x.Int() == y.Int()
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return x.Uint() == y.Uint()
case Float32, Float64:
return x.Float() == y.Float()
case Complex64, Complex128:
return x.Complex() == y.Complex()
case String:
return x.String() == y.String()
case Chan, Ptr, UnsafePointer:
return x.UnsafePointer() == y.UnsafePointer()
case Array:
for i := 0; i < x.Len(); i++ {
if !reflectValueEqual(x.Index(i), y.Index(i)) {
return false
}
}
return true
case Struct:
for i := 0; i < x.NumField(); i++ {
if !reflectValueEqual(x.Field(i), y.Field(i)) {
return false
}
}
return true
case Interface:
return reflectValueEqual(x.Elem(), y.Elem())
default:
runtimePanic("comparing un-comparable type")
return false // unreachable
}
}
166 changes: 166 additions & 0 deletions src/reflect/iter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package reflect

import "iter"

func rangeNum[T int8 | int16 | int32 | int64 | int |
uint8 | uint16 | uint32 | uint64 | uint |
uintptr, N int64 | uint64](v N) iter.Seq[Value] {
return func(yield func(v Value) bool) {
// cannot use range T(v) because no core type.
for i := T(0); i < T(v); i++ {
if !yield(ValueOf(i)) {
return
}
}
}
}

// Seq returns an iter.Seq[Value] that loops over the elements of v.
// If v's kind is Func, it must be a function that has no results and
// that takes a single argument of type func(T) bool for some type T.
// If v's kind is Pointer, the pointer element type must have kind Array.
// Otherwise v's kind must be Int, Int8, Int16, Int32, Int64,
// Uint, Uint8, Uint16, Uint32, Uint64, Uintptr,
// Array, Chan, Map, Slice, or String.
func (v Value) Seq() iter.Seq[Value] {
// TODO: canRangeFunc
// if canRangeFunc(v.typ()) {
// return func(yield func(Value) bool) {
// rf := MakeFunc(v.Type().In(0), func(in []Value) []Value {
// return []Value{ValueOf(yield(in[0]))}
// })
// v.Call([]Value{rf})
// }
// }
switch v.Kind() {
case Int:
return rangeNum[int](v.Int())
case Int8:
return rangeNum[int8](v.Int())
case Int16:
return rangeNum[int16](v.Int())
case Int32:
return rangeNum[int32](v.Int())
case Int64:
return rangeNum[int64](v.Int())
case Uint:
return rangeNum[uint](v.Uint())
case Uint8:
return rangeNum[uint8](v.Uint())
case Uint16:
return rangeNum[uint16](v.Uint())
case Uint32:
return rangeNum[uint32](v.Uint())
case Uint64:
return rangeNum[uint64](v.Uint())
case Uintptr:
return rangeNum[uintptr](v.Uint())
case Pointer:
if v.Elem().Kind() != Array {
break
}
return func(yield func(Value) bool) {
v = v.Elem()
for i := 0; i < v.Len(); i++ {
if !yield(ValueOf(i)) {
return
}
}
}
case Array, Slice:
return func(yield func(Value) bool) {
for i := 0; i < v.Len(); i++ {
if !yield(ValueOf(i)) {
return
}
}
}
case String:
return func(yield func(Value) bool) {
for i := range v.String() {
if !yield(ValueOf(i)) {
return
}
}
}
case Map:
return func(yield func(Value) bool) {
i := v.MapRange()
for i.Next() {
if !yield(i.Key()) {
return
}
}
}
case Chan:
return func(yield func(Value) bool) {
for value, ok := v.Recv(); ok; value, ok = v.Recv() {
if !yield(value) {
return
}
}
}
}
panic("reflect: " + v.Type().String() + " cannot produce iter.Seq[Value]")
}

// Seq2 returns an iter.Seq2[Value, Value] that loops over the elements of v.
// If v's kind is Func, it must be a function that has no results and
// that takes a single argument of type func(K, V) bool for some type K, V.
// If v's kind is Pointer, the pointer element type must have kind Array.
// Otherwise v's kind must be Array, Map, Slice, or String.
func (v Value) Seq2() iter.Seq2[Value, Value] {
// TODO: canRangeFunc2
// if canRangeFunc2(v.typ()) {
// return func(yield func(Value, Value) bool) {
// rf := MakeFunc(v.Type().In(0), func(in []Value) []Value {
// return []Value{ValueOf(yield(in[0], in[1]))}
// })
// v.Call([]Value{rf})
// }
// }
switch v.Kind() {
case Pointer:
if v.Elem().Kind() != Array {
break
}
return func(yield func(Value, Value) bool) {
v = v.Elem()
for i := 0; i < v.Len(); i++ {
if !yield(ValueOf(i), v.Index(i)) {
return
}
}
}
case Array, Slice:
return func(yield func(Value, Value) bool) {
for i := 0; i < v.Len(); i++ {
if !yield(ValueOf(i), v.Index(i)) {
return
}
}
}
case String:
return func(yield func(Value, Value) bool) {
for i, v := range v.String() {
if !yield(ValueOf(i), ValueOf(v)) {
return
}
}
}
case Map:
return func(yield func(Value, Value) bool) {
i := v.MapRange()
for i.Next() {
if !yield(i.Key(), i.Value()) {
return
}
}
}
}
panic("reflect: " + v.Type().String() + " cannot produce iter.Seq2[Value, Value]")
}
32 changes: 32 additions & 0 deletions src/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,12 @@ type Type interface {
// OverflowUint reports whether the uint64 x cannot be represented by type t.
// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64.
OverflowUint(x uint64) bool

// CanSeq reports whether a [Value] with this type can be iterated over using [Value.Seq].
CanSeq() bool

// CanSeq2 reports whether a [Value] with this type can be iterated over using [Value.Seq2].
CanSeq2() bool
}

// Constants for the 'meta' byte.
Expand Down Expand Up @@ -1183,6 +1189,32 @@ func (t rawType) OverflowUint(x uint64) bool {
panic("reflect: OverflowUint of non-uint type")
}

func (t rawType) CanSeq() bool {
switch t.Kind() {
case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map:
return true
case Func:
return false // TODO: implement canRangeFunc
// return canRangeFunc(&t.)
case Pointer:
return t.Elem().Kind() == Array
}
return false
}

func (t rawType) CanSeq2() bool {
switch t.Kind() {
case Array, Slice, String, Map:
return true
case Func:
return false // TODO: implement canRangeFunc2
// return canRangeFunc2(&t.t)
case Pointer:
return t.Elem().Kind() == Array
}
return false
}

func (t rawType) Method(i int) Method {
panic("unimplemented: (reflect.Type).Method()")
}
Expand Down
Loading
Loading