feat: Add support for named types with underlying basic types (#236)

## Why this should be merged

The current rlpgen partially supports named types with basic underlying,
and it generates the rlp without correct conversion i.e:
`w.WriteUint64(obj.Uint64NewT)`

This PR adds the full support it

## How this works

Adds check for `isNamedWithBasicUnderlying` and then returns a `op` with
`makeNamedBasicOp` which is basically a `basicOp` but with the `typ`
refers to the named type so that it can correctly encode decode with the
named type.

## How this was tested

Added UT tests

---------

Signed-off-by: Ceyhun Onur <ceyhunonur54@gmail.com>
This commit is contained in:
Ceyhun Onur 2025-10-08 10:07:57 +03:00 committed by GitHub
parent 910e897c54
commit 9ed36b613a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 126 additions and 1 deletions

View file

@ -687,6 +687,12 @@ func (bctx *buildContext) makeOp(name *types.Named, typ types.Type, tags rlpstru
if bctx.isDecoder(typ) {
return nil, fmt.Errorf("type %v implements rlp.Decoder with non-pointer receiver", typ)
}
if hasBasicUnderlying(typ) {
// libevm: named types are reduced to their underlying basic type in this loop.
// We're handling named types here by passing the named type as the main type.
// See [named.libevm.go] for more details.
return bctx.makeNamedBasicOp(typ)
}
// TODO: same check for encoder?
return bctx.makeOp(typ, typ.Underlying(), tags)
case *types.Pointer:

View file

@ -47,7 +47,7 @@ func init() {
}
}
var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint", "uint256", "alias"}
var tests = []string{"uints", "nil", "rawvalue", "optional", "bigint", "uint256", "alias", "named.libevm"}
func TestOutput(t *testing.T) {
for _, test := range tests {

View file

@ -0,0 +1,55 @@
// Copyright 2025 the libevm authors.
//
// The libevm additions to go-ethereum are free software: you can redistribute
// them and/or modify them under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The libevm additions are distributed in the hope that they will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"go/types"
)
// makeNamedBasicOp is a convenience wrapper for basicOp.
// It returns a basicOp with the named type as the main type instead of the underlying basic type.
func (bctx *buildContext) makeNamedBasicOp(named *types.Named) (op, error) {
underlying := named.Underlying()
basic, ok := underlying.(*types.Basic)
if !ok {
return nil, fmt.Errorf("expected basic type, got %T", underlying)
}
// We use basic op because it actually supports necessary conversions (through writeNeedsConversion and decodeNeedsConversion)
// for named types.
// The only problem with that is it does not support the named type as the main type.
// So we use the named type as the main type instead of the underlying basic type.
baseOp, err := bctx.makeBasicOp(basic)
if err != nil {
return nil, err
}
op, ok := baseOp.(basicOp)
if !ok {
return nil, fmt.Errorf("expected basicOp, got %T", baseOp)
}
op.typ = named
return op, nil
}
// hasBasicUnderlying checks whether `named` has an underlying basic type.
func hasBasicUnderlying(named *types.Named) bool {
_, ok := named.Underlying().(*types.Basic)
return ok
}

15
rlp/rlpgen/testdata/named.libevm.in.txt vendored Normal file
View file

@ -0,0 +1,15 @@
// -*- mode: go -*-
package test
type (
BoolT bool
Uint64T uint64
StringT string
)
type Test struct {
BoolNewT BoolT
Uint64NewT Uint64T
StringNewT StringT
}

View file

@ -0,0 +1,49 @@
package test
import "github.com/ava-labs/libevm/rlp"
import "io"
func (obj *Test) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List()
w.WriteBool(bool(obj.BoolNewT))
w.WriteUint64(uint64(obj.Uint64NewT))
w.WriteString(string(obj.StringNewT))
w.ListEnd(_tmp0)
return w.Flush()
}
func (obj *Test) DecodeRLP(dec *rlp.Stream) error {
var _tmp0 Test
{
if _, err := dec.List(); err != nil {
return err
}
// BoolNewT:
_tmp1, err := dec.Bool()
if err != nil {
return err
}
_tmp2 := BoolT(_tmp1)
_tmp0.BoolNewT = _tmp2
// Uint64NewT:
_tmp3, err := dec.Uint64()
if err != nil {
return err
}
_tmp4 := Uint64T(_tmp3)
_tmp0.Uint64NewT = _tmp4
// StringNewT:
_tmp5, err := dec.String()
if err != nil {
return err
}
_tmp6 := StringT(_tmp5)
_tmp0.StringNewT = _tmp6
if err := dec.ListEnd(); err != nil {
return err
}
}
*obj = _tmp0
return nil
}