mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
doc: libevm/pseudo package comments and improved readability
This commit is contained in:
parent
35c8988d4f
commit
a796dcfeb0
4 changed files with 134 additions and 71 deletions
|
|
@ -1,4 +1,15 @@
|
|||
// Package pseudo ...
|
||||
// Package pseudo provides a bridge between generic and non-generic code via
|
||||
// pseudo-types and pseudo-values. With careful usage, there is minimal
|
||||
// reduction in type safety.
|
||||
//
|
||||
// Adding generic type parameters to anything (e.g. struct, function, etc)
|
||||
// "pollutes" all code that uses the generic type. Refactoring all uses isn't
|
||||
// always feasible, and a [Type] acts as an intermediate fix. Although their
|
||||
// constructors are generic, they are not, and they are instead coupled with a
|
||||
// generic [Value] that SHOULD be used for access.
|
||||
//
|
||||
// Packages typically SHOULD NOT expose a [Type] and SHOULD instead provide
|
||||
// users with a type-safe [Value].
|
||||
package pseudo
|
||||
|
||||
import (
|
||||
|
|
@ -6,63 +17,101 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// Zero ...
|
||||
func Zero[T any]() (*Type, *Value[T]) {
|
||||
var x T
|
||||
return From[T](x)
|
||||
}
|
||||
|
||||
// From ...
|
||||
func From[T any](x T) (*Type, *Value[T]) {
|
||||
t := &Type{
|
||||
val: &concrete[T]{
|
||||
val: x,
|
||||
},
|
||||
}
|
||||
return t, NewValueUnsafe[T](t)
|
||||
}
|
||||
|
||||
// OnlyType ...
|
||||
func OnlyType[T any](t *Type, _ *Value[T]) *Type {
|
||||
return t
|
||||
}
|
||||
|
||||
// A Type ...
|
||||
// A Type wraps a strongly-typed value without exposing information about its
|
||||
// type. It can be used in lieu of a generic field / parameter.
|
||||
type Type struct {
|
||||
val value
|
||||
}
|
||||
|
||||
func (t *Type) Interface() any { return t.val.get() }
|
||||
|
||||
// func (t *Type) Set(v any) error { return t.val.Set(v) }
|
||||
// func (t *Type) MustSet(v any) { t.val.MustSet(v) }
|
||||
func (t *Type) MarshalJSON() ([]byte, error) { return t.val.MarshalJSON() }
|
||||
func (t *Type) UnmarshalJSON(b []byte) error { return t.val.UnmarshalJSON(b) }
|
||||
|
||||
var (
|
||||
_ json.Marshaler = (*Type)(nil)
|
||||
_ json.Unmarshaler = (*Type)(nil)
|
||||
)
|
||||
|
||||
func NewValueUnsafe[T any](t *Type) *Value[T] {
|
||||
return &Value[T]{t: t}
|
||||
}
|
||||
|
||||
func NewValue[T any](t *Type) (*Value[T], error) {
|
||||
var x T
|
||||
if !t.val.canSetTo(x) {
|
||||
return nil, fmt.Errorf("cannot create *Accessor[%T] with *Type carrying %T", x, t.val.get())
|
||||
}
|
||||
return NewValueUnsafe[T](t), nil
|
||||
}
|
||||
|
||||
// A Value provides strongly-typed access to the payload carried by a [Type].
|
||||
type Value[T any] struct {
|
||||
t *Type
|
||||
}
|
||||
|
||||
// A Pseudo type couples a [Type] and a [Value]. If returned by a constructor
|
||||
// from this package, both wrap the same payload.
|
||||
type Pseudo[T any] struct {
|
||||
Type *Type
|
||||
Value *Value[T]
|
||||
}
|
||||
|
||||
// TypeAndValue is a convenience function for splitting the contents of `p`,
|
||||
// typically at construction.
|
||||
func (p *Pseudo[T]) TypeAndValue() (*Type, *Value[T]) {
|
||||
return p.Type, p.Value
|
||||
}
|
||||
|
||||
// From returns a Pseudo[T] constructed from `v`.
|
||||
func From[T any](v T) *Pseudo[T] {
|
||||
t := &Type{
|
||||
val: &concrete[T]{
|
||||
val: v,
|
||||
},
|
||||
}
|
||||
return &Pseudo[T]{t, MustNewValue[T](t)}
|
||||
}
|
||||
|
||||
// Zero is equivalent to [From] called with the [zero value] of type `T`. Note
|
||||
// that pointers, slices, maps, etc. will therefore be nil.
|
||||
//
|
||||
// [zero value]: https://go.dev/tour/basics/12
|
||||
func Zero[T any]() *Pseudo[T] {
|
||||
var x T
|
||||
return From[T](x)
|
||||
}
|
||||
|
||||
// Interface returns the wrapped value as an `any`, equivalent to
|
||||
// [reflect.Value.Interface]. Prefer [Value.Get].
|
||||
func (t *Type) Interface() any { return t.val.get() }
|
||||
|
||||
// NewValue constructs a [Value] from a [Type], first confirming that `t` wraps
|
||||
// a payload of type `T`.
|
||||
func NewValue[T any](t *Type) (*Value[T], error) {
|
||||
var x T
|
||||
if !t.val.canSetTo(x) {
|
||||
return nil, fmt.Errorf("cannot create *Value[%T] with *Type carrying %T", x, t.val.get())
|
||||
}
|
||||
return &Value[T]{t}, nil
|
||||
}
|
||||
|
||||
// MustNewValue is equivalent to [NewValue] except that it panics instead of
|
||||
// returning an error.
|
||||
func MustNewValue[T any](t *Type) *Value[T] {
|
||||
v, err := NewValue[T](t)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Get returns the value.
|
||||
func (a *Value[T]) Get() T { return a.t.val.get().(T) }
|
||||
|
||||
// Set sets the value.
|
||||
func (a *Value[T]) Set(v T) { a.t.val.mustSet(v) }
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (t *Type) MarshalJSON() ([]byte, error) { return t.val.MarshalJSON() }
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (t *Type) UnmarshalJSON(b []byte) error { return t.val.UnmarshalJSON(b) }
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (v *Value[T]) MarshalJSON() ([]byte, error) { return v.t.MarshalJSON() }
|
||||
|
||||
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||
func (v *Value[T]) UnmarshalJSON(b []byte) error { return v.t.UnmarshalJSON(b) }
|
||||
|
||||
var _ = []interface {
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
}{
|
||||
(*Type)(nil),
|
||||
(*Value[struct{}])(nil),
|
||||
(*concrete[struct{}])(nil),
|
||||
}
|
||||
|
||||
// A value is a non-generic wrapper around a [concrete] struct.
|
||||
type value interface {
|
||||
get() any
|
||||
canSetTo(any) bool
|
||||
|
|
@ -84,11 +133,14 @@ func (c *concrete[T]) canSetTo(v any) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
type InvalidTypeError[T any] struct {
|
||||
// An invalidTypeError is returned by [conrete.set] if the value is incompatible
|
||||
// with its type. This should never leave this package and exists only to
|
||||
// provide precise testing of unhappy paths.
|
||||
type invalidTypeError[T any] struct {
|
||||
SetTo any
|
||||
}
|
||||
|
||||
func (e *InvalidTypeError[T]) Error() string {
|
||||
func (e *invalidTypeError[T]) Error() string {
|
||||
var t T
|
||||
return fmt.Sprintf("cannot set %T to %T", t, e.SetTo)
|
||||
}
|
||||
|
|
@ -96,7 +148,9 @@ func (e *InvalidTypeError[T]) Error() string {
|
|||
func (c *concrete[T]) set(v any) error {
|
||||
vv, ok := v.(T)
|
||||
if !ok {
|
||||
return &InvalidTypeError[T]{SetTo: v}
|
||||
// Other invariants in this implementation (aim to) guarantee that this
|
||||
// will never happen.
|
||||
return &invalidTypeError[T]{SetTo: v}
|
||||
}
|
||||
c.val = vv
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func TestType(t *testing.T) {
|
|||
|
||||
testType(
|
||||
t, "From[uint](314159)",
|
||||
func() (*Type, *Value[uint]) {
|
||||
func() *Pseudo[uint] {
|
||||
return From[uint](314159)
|
||||
},
|
||||
314159, 0, struct{}{},
|
||||
|
|
@ -24,20 +24,20 @@ func TestType(t *testing.T) {
|
|||
testType(t, "nil pointer", Zero[*float64], (*float64)(nil), new(float64), 0)
|
||||
}
|
||||
|
||||
func testType[T any](t *testing.T, name string, ctor func() (*Type, *Value[T]), init T, setTo T, invalid any) {
|
||||
func testType[T any](t *testing.T, name string, ctor func() *Pseudo[T], init T, setTo T, invalid any) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
typ, val := ctor()
|
||||
typ, val := ctor().TypeAndValue()
|
||||
assert.Equal(t, init, val.Get())
|
||||
val.Set(setTo)
|
||||
assert.Equal(t, setTo, val.Get())
|
||||
|
||||
t.Run("set to invalid type", func(t *testing.T) {
|
||||
wantErr := &InvalidTypeError[T]{SetTo: invalid}
|
||||
wantErr := &invalidTypeError[T]{SetTo: invalid}
|
||||
|
||||
assertError := func(t *testing.T, err any) {
|
||||
t.Helper()
|
||||
switch err := err.(type) {
|
||||
case *InvalidTypeError[T]:
|
||||
case *invalidTypeError[T]:
|
||||
assert.Equal(t, wantErr, err)
|
||||
default:
|
||||
t.Errorf("got error %v; want %v", err, wantErr)
|
||||
|
|
@ -60,9 +60,20 @@ func testType[T any](t *testing.T, name string, ctor func() (*Type, *Value[T]),
|
|||
buf, err := json.Marshal(typ)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, gotVal := Zero[T]()
|
||||
got, gotVal := Zero[T]().TypeAndValue()
|
||||
require.NoError(t, json.Unmarshal(buf, &got))
|
||||
assert.Equal(t, val.Get(), gotVal.Get())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func ExamplePseudo_TypeAndValue() {
|
||||
typ, val := From("hello").TypeAndValue()
|
||||
|
||||
// But, if only one is needed:
|
||||
typ = From("world").Type
|
||||
val = From("this isn't coupled to the Type").Value
|
||||
|
||||
_ = typ
|
||||
_ = val
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,12 +56,12 @@ type ExtraPayloadGetter[C any, R any] struct{}
|
|||
|
||||
// FromChainConfig ...
|
||||
func (ExtraPayloadGetter[C, R]) FromChainConfig(c *ChainConfig) *C {
|
||||
return pseudo.NewValueUnsafe[*C](c.extraPayload()).Get()
|
||||
return pseudo.MustNewValue[*C](c.extraPayload()).Get()
|
||||
}
|
||||
|
||||
// FromRules ...
|
||||
func (ExtraPayloadGetter[C, R]) FromRules(r *Rules) *R {
|
||||
return pseudo.NewValueUnsafe[*R](r.extraPayload()).Get()
|
||||
return pseudo.MustNewValue[*R](r.extraPayload()).Get()
|
||||
}
|
||||
|
||||
func mustBeStruct[T any]() {
|
||||
|
|
@ -160,19 +160,17 @@ func (r *Rules) extraPayload() *pseudo.Type {
|
|||
return r.extra
|
||||
}
|
||||
|
||||
func (Extras[C, R]) nilForChainConfig() *pseudo.Type { return pseudo.OnlyType(pseudo.Zero[*C]()) }
|
||||
func (Extras[C, R]) nilForRules() *pseudo.Type { return pseudo.OnlyType(pseudo.Zero[*R]()) }
|
||||
func (Extras[C, R]) nilForChainConfig() *pseudo.Type { return pseudo.Zero[*C]().Type }
|
||||
func (Extras[C, R]) nilForRules() *pseudo.Type { return pseudo.Zero[*R]().Type }
|
||||
|
||||
func (*Extras[C, R]) newForChainConfig() *pseudo.Type {
|
||||
var x C
|
||||
return pseudo.OnlyType(pseudo.From(&x))
|
||||
return pseudo.From(&x).Type
|
||||
}
|
||||
|
||||
func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type {
|
||||
if e.NewForRules == nil {
|
||||
return e.nilForRules()
|
||||
}
|
||||
return pseudo.OnlyType(
|
||||
pseudo.From(e.NewForRules(c, r, c.extra.Interface().(*C), blockNum, isMerge, timestamp)),
|
||||
)
|
||||
return pseudo.From(e.NewForRules(c, r, c.extra.Interface().(*C), blockNum, isMerge, timestamp)).Type
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ func TestRegisterExtras(t *testing.T) {
|
|||
},
|
||||
})
|
||||
},
|
||||
ccExtra: pseudo.OnlyType(pseudo.From(&ccExtraA{
|
||||
ccExtra: pseudo.From(&ccExtraA{
|
||||
A: "hello",
|
||||
})),
|
||||
}).Type,
|
||||
wantRulesExtra: &rulesExtraA{
|
||||
A: "hello",
|
||||
},
|
||||
|
|
@ -68,9 +68,9 @@ func TestRegisterExtras(t *testing.T) {
|
|||
register: func() {
|
||||
RegisterExtras(Extras[ccExtraB, rulesExtraB]{})
|
||||
},
|
||||
ccExtra: pseudo.OnlyType(pseudo.From(&ccExtraB{
|
||||
ccExtra: pseudo.From(&ccExtraB{
|
||||
B: "world",
|
||||
})),
|
||||
}).Type,
|
||||
wantRulesExtra: (*rulesExtraB)(nil),
|
||||
},
|
||||
{
|
||||
|
|
@ -78,9 +78,9 @@ func TestRegisterExtras(t *testing.T) {
|
|||
register: func() {
|
||||
RegisterExtras(Extras[rawJSON, struct{}]{})
|
||||
},
|
||||
ccExtra: pseudo.OnlyType(pseudo.From(&rawJSON{
|
||||
ccExtra: pseudo.From(&rawJSON{
|
||||
RawMessage: []byte(`"hello, world"`),
|
||||
})),
|
||||
}).Type,
|
||||
wantRulesExtra: (*struct{})(nil),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue