mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22: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
|
package pseudo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -6,63 +17,101 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Zero ...
|
// A Type wraps a strongly-typed value without exposing information about its
|
||||||
func Zero[T any]() (*Type, *Value[T]) {
|
// type. It can be used in lieu of a generic field / parameter.
|
||||||
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 ...
|
|
||||||
type Type struct {
|
type Type struct {
|
||||||
val value
|
val value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Type) Interface() any { return t.val.get() }
|
// A Value provides strongly-typed access to the payload carried by a [Type].
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
type Value[T any] struct {
|
type Value[T any] struct {
|
||||||
t *Type
|
t *Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Value[T]) Get() T { return a.t.val.get().(T) }
|
// 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) }
|
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 {
|
type value interface {
|
||||||
get() any
|
get() any
|
||||||
canSetTo(any) bool
|
canSetTo(any) bool
|
||||||
|
|
@ -84,11 +133,14 @@ func (c *concrete[T]) canSetTo(v any) bool {
|
||||||
return ok
|
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
|
SetTo any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *InvalidTypeError[T]) Error() string {
|
func (e *invalidTypeError[T]) Error() string {
|
||||||
var t T
|
var t T
|
||||||
return fmt.Sprintf("cannot set %T to %T", t, e.SetTo)
|
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 {
|
func (c *concrete[T]) set(v any) error {
|
||||||
vv, ok := v.(T)
|
vv, ok := v.(T)
|
||||||
if !ok {
|
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
|
c.val = vv
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ func TestType(t *testing.T) {
|
||||||
|
|
||||||
testType(
|
testType(
|
||||||
t, "From[uint](314159)",
|
t, "From[uint](314159)",
|
||||||
func() (*Type, *Value[uint]) {
|
func() *Pseudo[uint] {
|
||||||
return From[uint](314159)
|
return From[uint](314159)
|
||||||
},
|
},
|
||||||
314159, 0, struct{}{},
|
314159, 0, struct{}{},
|
||||||
|
|
@ -24,20 +24,20 @@ func TestType(t *testing.T) {
|
||||||
testType(t, "nil pointer", Zero[*float64], (*float64)(nil), new(float64), 0)
|
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) {
|
t.Run(name, func(t *testing.T) {
|
||||||
typ, val := ctor()
|
typ, val := ctor().TypeAndValue()
|
||||||
assert.Equal(t, init, val.Get())
|
assert.Equal(t, init, val.Get())
|
||||||
val.Set(setTo)
|
val.Set(setTo)
|
||||||
assert.Equal(t, setTo, val.Get())
|
assert.Equal(t, setTo, val.Get())
|
||||||
|
|
||||||
t.Run("set to invalid type", func(t *testing.T) {
|
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) {
|
assertError := func(t *testing.T, err any) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case *InvalidTypeError[T]:
|
case *invalidTypeError[T]:
|
||||||
assert.Equal(t, wantErr, err)
|
assert.Equal(t, wantErr, err)
|
||||||
default:
|
default:
|
||||||
t.Errorf("got error %v; want %v", err, wantErr)
|
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)
|
buf, err := json.Marshal(typ)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
got, gotVal := Zero[T]()
|
got, gotVal := Zero[T]().TypeAndValue()
|
||||||
require.NoError(t, json.Unmarshal(buf, &got))
|
require.NoError(t, json.Unmarshal(buf, &got))
|
||||||
assert.Equal(t, val.Get(), gotVal.Get())
|
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 ...
|
// FromChainConfig ...
|
||||||
func (ExtraPayloadGetter[C, R]) FromChainConfig(c *ChainConfig) *C {
|
func (ExtraPayloadGetter[C, R]) FromChainConfig(c *ChainConfig) *C {
|
||||||
return pseudo.NewValueUnsafe[*C](c.extraPayload()).Get()
|
return pseudo.MustNewValue[*C](c.extraPayload()).Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromRules ...
|
// FromRules ...
|
||||||
func (ExtraPayloadGetter[C, R]) FromRules(r *Rules) *R {
|
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]() {
|
func mustBeStruct[T any]() {
|
||||||
|
|
@ -160,19 +160,17 @@ func (r *Rules) extraPayload() *pseudo.Type {
|
||||||
return r.extra
|
return r.extra
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Extras[C, R]) nilForChainConfig() *pseudo.Type { return pseudo.OnlyType(pseudo.Zero[*C]()) }
|
func (Extras[C, R]) nilForChainConfig() *pseudo.Type { return pseudo.Zero[*C]().Type }
|
||||||
func (Extras[C, R]) nilForRules() *pseudo.Type { return pseudo.OnlyType(pseudo.Zero[*R]()) }
|
func (Extras[C, R]) nilForRules() *pseudo.Type { return pseudo.Zero[*R]().Type }
|
||||||
|
|
||||||
func (*Extras[C, R]) newForChainConfig() *pseudo.Type {
|
func (*Extras[C, R]) newForChainConfig() *pseudo.Type {
|
||||||
var x C
|
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 {
|
func (e *Extras[C, R]) newForRules(c *ChainConfig, r *Rules, blockNum *big.Int, isMerge bool, timestamp uint64) *pseudo.Type {
|
||||||
if e.NewForRules == nil {
|
if e.NewForRules == nil {
|
||||||
return e.nilForRules()
|
return e.nilForRules()
|
||||||
}
|
}
|
||||||
return pseudo.OnlyType(
|
return pseudo.From(e.NewForRules(c, r, c.extra.Interface().(*C), blockNum, isMerge, timestamp)).Type
|
||||||
pseudo.From(e.NewForRules(c, r, c.extra.Interface().(*C), blockNum, isMerge, timestamp)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,9 @@ func TestRegisterExtras(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
ccExtra: pseudo.OnlyType(pseudo.From(&ccExtraA{
|
ccExtra: pseudo.From(&ccExtraA{
|
||||||
A: "hello",
|
A: "hello",
|
||||||
})),
|
}).Type,
|
||||||
wantRulesExtra: &rulesExtraA{
|
wantRulesExtra: &rulesExtraA{
|
||||||
A: "hello",
|
A: "hello",
|
||||||
},
|
},
|
||||||
|
|
@ -68,9 +68,9 @@ func TestRegisterExtras(t *testing.T) {
|
||||||
register: func() {
|
register: func() {
|
||||||
RegisterExtras(Extras[ccExtraB, rulesExtraB]{})
|
RegisterExtras(Extras[ccExtraB, rulesExtraB]{})
|
||||||
},
|
},
|
||||||
ccExtra: pseudo.OnlyType(pseudo.From(&ccExtraB{
|
ccExtra: pseudo.From(&ccExtraB{
|
||||||
B: "world",
|
B: "world",
|
||||||
})),
|
}).Type,
|
||||||
wantRulesExtra: (*rulesExtraB)(nil),
|
wantRulesExtra: (*rulesExtraB)(nil),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -78,9 +78,9 @@ func TestRegisterExtras(t *testing.T) {
|
||||||
register: func() {
|
register: func() {
|
||||||
RegisterExtras(Extras[rawJSON, struct{}]{})
|
RegisterExtras(Extras[rawJSON, struct{}]{})
|
||||||
},
|
},
|
||||||
ccExtra: pseudo.OnlyType(pseudo.From(&rawJSON{
|
ccExtra: pseudo.From(&rawJSON{
|
||||||
RawMessage: []byte(`"hello, world"`),
|
RawMessage: []byte(`"hello, world"`),
|
||||||
})),
|
}).Type,
|
||||||
wantRulesExtra: (*struct{})(nil),
|
wantRulesExtra: (*struct{})(nil),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue