Skip to content

Commit

Permalink
feat(stdlibs): remove support for linkedType in native bindings (#1700)
Browse files Browse the repository at this point in the history
Split from #1695 for ease of reviewing. Related to #814. Merge order:

1. #1700 (this one!) 
2. #1702
3. #1695

I scrapped the "linked identifier" feature of native bindings. The
reasoning mostly stems from the changes subsequently implemented in
#1695, as having "linked identifiers" means that:

- the Gno linked types must have a different name from Go's if they are
within the same package, so their names don't conflict after
transpilation
- in order for the "linked types" to match in the generated code, the
AST has to be rewritten to make a type alias (ie. `type Address =
crypto.Bech32Address`)
- while still allowing the type to be "modifiable" by the std package,
because we want to add our own methods -- this is not possible for
imported types, obviously
- and if we try removing methods, this [creates
errors](https://drop.howl.moe/9ba0d53115-Screenshot_from_2024-02-27_23-50-34.png)
because the methods on the original types don't match 1-1 those
implemented in AST

Although, I think a decent case can be made besides easing our (my) life
in transpiling:

- Under the hood, the linked type mechanism works with
`Store.AddGno2GoMapping`. This uses gonative (read: the bane of my
existence -- #1361). If we remove all linked types, we can still pass
data between Go and Gno using type literals, which don't incur in naming
conflicts.
- This also makes the workings of "native bindings" clearer in general,
as they don't have any special linked types (which were currently
hardcoded in the misc/genstd source)

## Reviewing notes

- `Banker` got severely refactored as it was mapping entire go
interfaces into Go; it now uses simple elementary functions, with its
old behaviour split between Go and Gno.
- many other functions (std/native.gno) have also been changed so that
their native function only uses primitive types (so everything that used
an Address, now uses a string).
- Due to the naming conflicts already mentioned, Go's Banker has been
changed to `BankerInterface` so that it doesn't conflict.
- AddGo2GnoMapping is unused in the codebase. This has been removed from
Store to disencourage any further usage; removal of Go2Gno code is out
of scope for this PR (see #1361)
  • Loading branch information
thehowl authored Mar 18, 2024
1 parent 01e91be commit 0f2e755
Show file tree
Hide file tree
Showing 21 changed files with 696 additions and 1,004 deletions.
1 change: 0 additions & 1 deletion gno.land/pkg/sdk/vm/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) {
}
store.SetPackageGetter(getPackage)
store.SetNativeStore(stdlibs.NativeStore)
stdlibs.InjectNativeMappings(store)
}

// ----------------------------------------
Expand Down
109 changes: 43 additions & 66 deletions gnovm/pkg/gnolang/gonative.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,6 @@ func (ds *defaultStore) SetStrictGo2GnoMapping(strict bool) {
ds.go2gnoStrict = strict
}

// Implements Store.
func (ds *defaultStore) AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) {
rtPkgPath := rt.PkgPath()
if rtPkgPath == "" {
panic(fmt.Sprintf("type has no associated package path: %v", rt))
}
rtName := rt.Name()
if rtName == "" {
panic(fmt.Sprintf("type has no name: %v", rt))
}
ds.go2gnoMap[rtPkgPath+"."+rtName] = pkgPath + "." + name
}

// Implements Store.
// See go2GnoValue2(). Like go2GnoType() but also converts any
// top-level complex types (or pointers to them). The result gets
Expand All @@ -110,9 +97,6 @@ func (ds *defaultStore) AddGo2GnoMapping(rt reflect.Type, pkgPath string, name s
// The namedness of the native type gets converted to an
// appropriate gno *DeclaredType with native methods
// converted via go2GnoFuncType().
// If store is not nil, native named types do not construct new
// gno types, but rather the store is used to fetch gno types
// from ds.go2gnoMap.
func (ds *defaultStore) Go2GnoType(rt reflect.Type) (t Type) {
if gnot, ok := ds.cacheNativeTypes[rt]; ok {
return gnot
Expand All @@ -125,61 +109,54 @@ func (ds *defaultStore) Go2GnoType(rt reflect.Type) (t Type) {
// wrap t with declared type.
pkgPath := rt.PkgPath()
if pkgPath != "" {
// try to look up gno type from mapping.
gokey := pkgPath + "." + rt.Name()
gnokey, ok := ds.go2gnoMap[gokey]
if ok {
// mapping successful.
typ := ds.GetType(TypeID(gnokey))
if typ == nil {
panic(fmt.Sprintf("missing type %s", gnokey))
}
t = typ
} else if ds.go2gnoStrict {
// mappings have been removed, so for any non-builtin type in strict mode,
// this will panic.
if ds.go2gnoStrict {
// mapping failed and strict: error.
panic(fmt.Sprintf("native type mapping missing for %s", gokey))
gokey := pkgPath + "." + rt.Name()
panic(fmt.Sprintf("native type does not exist for %s", gokey))
}

// generate a new gno type for testing.
mtvs := []TypedValue(nil)
if t.Kind() == InterfaceKind {
// methods already set on t.Methods.
// *DT.Methods not used in Go for interfaces.
} else {
// generate a new gno type for testing.
mtvs := []TypedValue(nil)
if t.Kind() == InterfaceKind {
// methods already set on t.Methods.
// *DT.Methods not used in Go for interfaces.
} else {
prt := rt
if rt.Kind() != reflect.Ptr {
// NOTE: go reflect requires ptr kind
// for methods with ptr receivers,
// whereas gno methods are all
// declared on the *DeclaredType.
prt = reflect.PtrTo(rt)
}
nm := prt.NumMethod()
mtvs = make([]TypedValue, nm)
for i := 0; i < nm; i++ {
mthd := prt.Method(i)
ft := ds.go2GnoFuncType(mthd.Type)
fv := &FuncValue{
Type: ft,
IsMethod: true,
Source: nil,
Name: Name(mthd.Name),
Closure: nil,
PkgPath: pkgPath,
body: nil, // XXX
nativeBody: nil,
}
mtvs[i] = TypedValue{T: ft, V: fv}
}
prt := rt
if rt.Kind() != reflect.Ptr {
// NOTE: go reflect requires ptr kind
// for methods with ptr receivers,
// whereas gno methods are all
// declared on the *DeclaredType.
prt = reflect.PtrTo(rt)
}
dt := &DeclaredType{
PkgPath: pkgPath,
Name: Name(rt.Name()),
Base: t,
Methods: mtvs,
nm := prt.NumMethod()
mtvs = make([]TypedValue, nm)
for i := 0; i < nm; i++ {
mthd := prt.Method(i)
ft := ds.go2GnoFuncType(mthd.Type)
fv := &FuncValue{
Type: ft,
IsMethod: true,
Source: nil,
Name: Name(mthd.Name),
Closure: nil,
PkgPath: pkgPath,
body: nil, // XXX
nativeBody: nil,
}
mtvs[i] = TypedValue{T: ft, V: fv}
}
dt.Seal()
t = dt
}
dt := &DeclaredType{
PkgPath: pkgPath,
Name: Name(rt.Name()),
Base: t,
Methods: mtvs,
}
dt.Seal()
t = dt
}
// memoize t to cache.
if debug {
Expand Down
4 changes: 0 additions & 4 deletions gnovm/pkg/gnolang/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ type Store interface {
SetBlockNode(BlockNode)
// UNSTABLE
SetStrictGo2GnoMapping(bool)
AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string)
Go2GnoType(rt reflect.Type) Type
GetAllocator() *Allocator
NumMemPackages() int64
Expand Down Expand Up @@ -76,7 +75,6 @@ type defaultStore struct {
iavlStore store.Store // for escaped object hashes
pkgInjector PackageInjector // for injecting natives
nativeStore NativeStore // for injecting natives
go2gnoMap map[string]string // go pkgpath.name -> gno pkgpath.name
go2gnoStrict bool // if true, native->gno type conversion must be registered.

// transient
Expand All @@ -94,7 +92,6 @@ func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore
cacheNativeTypes: make(map[reflect.Type]Type),
baseStore: baseStore,
iavlStore: iavlStore,
go2gnoMap: make(map[string]string),
go2gnoStrict: true,
current: make(map[string]struct{}),
}
Expand Down Expand Up @@ -608,7 +605,6 @@ func (ds *defaultStore) Fork() Store {
iavlStore: ds.iavlStore,
pkgInjector: ds.pkgInjector,
nativeStore: ds.nativeStore,
go2gnoMap: ds.go2gnoMap,
go2gnoStrict: ds.go2gnoStrict,
opslog: nil, // new ops log.
current: make(map[string]struct{}),
Expand Down
Loading

0 comments on commit 0f2e755

Please sign in to comment.