Skip to content
Open
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
40 changes: 25 additions & 15 deletions pkg/gnotypes/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,15 @@ func (ctx *cloneContext) CloneTypeWithNilPackage(t types.Type) types.Type {
// will be filled in after its components (underlying type, methods) are cloned.

newTypeName := types.NewTypeName(token.NoPos, nil /* pkg = nil */, origObj.Name(), nil)
// Create the Named with a nil underlying and cache it BEFORE cloning
// the underlying type and methods. A recursive self-reference (e.g. a
// method returning the same named type, like realm.Previous() realm)
// then resolves to this instance via cloneMap instead of cloning a
// divergent, method-incomplete copy. The underlying is filled in with
// SetUnderlying once cloned.
named := types.NewNamed(newTypeName, nil, nil)
ctx.cloneMap[T] = named

underlying := ctx.CloneTypeWithNilPackage(T.Underlying())
for {
if _, ok := underlying.(*types.Named); !ok {
Expand All @@ -271,23 +280,17 @@ func (ctx *cloneContext) CloneTypeWithNilPackage(t types.Type) types.Type {
// by its own NewNamed call during the recursive CloneTypeWithNilPackage.
underlying = underlying.Underlying()
}
named.SetUnderlying(underlying)

var newMethods []*types.Func
if T.NumMethods() > 0 {
newMethods = make([]*types.Func, T.NumMethods())
for i := range T.NumMethods() {
origMethod := T.Method(i)
// Clone the signature. Types within the signature will also be cloned.
clonedSig := ctx.CloneTypeWithNilPackage(origMethod.Type()).(*types.Signature)
// Create the new Func. types.NewNamed will set its Pkg based on newTypeName.Pkg (which is nil).
newMethods[i] = types.NewFunc(token.NoPos, nil /* pkg */, origMethod.Name(), clonedSig)
}
for i := range T.NumMethods() {
origMethod := T.Method(i)
// Clone the signature. Types within the signature will also be cloned.
clonedSig := ctx.CloneTypeWithNilPackage(origMethod.Type()).(*types.Signature)
// Create the new Func. types.NewFunc uses newTypeName.Pkg (nil).
named.AddMethod(types.NewFunc(token.NoPos, nil /* pkg */, origMethod.Name(), clonedSig))
}

// Construct the final *types.Named object.
// This will also correctly set newTypeName.obj and newNamedInstance.obj.
result = types.NewNamed(newTypeName, underlying, newMethods)
ctx.cloneMap[T] = result // Map original T to the placeholder
result = named

case *types.Pointer:
elem := ctx.CloneTypeWithNilPackage(T.Elem())
Expand Down Expand Up @@ -473,7 +476,14 @@ func init() {
for name, obj := range gnoBuiltin {
switch o := obj.(type) {
case *types.Func:
sig := o.Type().(*types.Signature)
// Clone the signature with the shared ctx so any custom builtin
// named types it references resolve to the same package-less
// instances registered in the Universe below — not the
// package-qualified originals from the parsed builtin file.
// Without this, cross(cur) fails to type-check: cross's param
// would be builtin.realm while the Universe realm is the cloned,
// package-less type (identity mismatch).
sig := ctx.CloneTypeWithNilPackage(o.Type()).(*types.Signature)
newFn := types.NewFunc(token.NoPos, nil, name, sig) // a builtin don't have a pos
types.Universe.Insert(newFn) // register func
log.Printf("builtin func %q has been registered", o.Name())
Expand Down
23 changes: 13 additions & 10 deletions pkg/gnotypes/builtin/builtin.gno
Original file line number Diff line number Diff line change
Expand Up @@ -341,16 +341,16 @@ type error interface {
// with `/r/`) can accept a `realm` parameter for crossing. Functions in regular
// `p/` packages (non-realm) cannot use this mechanism.

// cross is a custom built-in keyword passed along when calling
// a crossing function in a different realm. For an exposed function to
// be crossing, its first argument must be of type "realm", ie
// cross marks an explicit cross-realm call. For an exposed function to be
// crossing, its first argument must be of type "realm", ie
//
// func MyCrossingFunc(_ realm, ...) {...}
// func MyCrossingFunc(cur realm, ...) {...}
//
// Then, calling this function from a different realm is done as so:
// Then, calling this function from a different realm is done by applying cross
// to the current realm value and passing the result as the first argument:
//
// anotherApp.MyCrossingFunc(cross, ...)
var cross realm
// anotherApp.MyCrossingFunc(cross(cur), ...)
func cross(rlm realm) realm { return rlm }

// address is an alias to std.Address
type address string
Expand All @@ -371,10 +371,13 @@ type gnocoins []gnocoin
type realm interface {
Address() address
PkgPath() string
Coins() gnocoins
Send(coins gnocoins, to address) error
Previous() realm
Origin() realm
IsCode() bool
IsUser() bool
IsUserCall() bool
IsUserRun() bool
IsEphemeral() bool
IsCurrent() bool
String() string
}

Expand Down
34 changes: 32 additions & 2 deletions pkg/gnotypes/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ func itscrossing(_ realm) {
}
})

t.Run("cross call", func(t *testing.T) {
// cross is a function in the interrealm v2 (gno 0.9) semantics:
// cross(cur) is passed as the first arg of a crossing call.
content := fmt.Sprintf(`package %s
func Crossing(cur realm) {}
func Caller(cur realm) {
Crossing(cross(cur))
}`, pkgName)
process(pkgName, content)
})

t.Run("realm methods", func(t *testing.T) {
// The realm interface must expose the gno 0.9 ACL predicates so
// code like cur.IsUserCall() / cur.Previous().IsCurrent() resolves.
content := fmt.Sprintf(`package %s
func Uses(cur realm) {
_ = cur.Address()
_ = cur.PkgPath()
_ = cur.IsCode()
_ = cur.IsUser()
_ = cur.IsUserCall()
_ = cur.IsUserRun()
_ = cur.IsEphemeral()
_ = cur.IsCurrent()
_ = cur.Previous().IsUserCall()
_ = cur.String()
}`, pkgName)
process(pkgName, content)
})

t.Run("revive usage", func(t *testing.T) {
content := fmt.Sprintf(`package %s
func TestRevive() {
Expand Down Expand Up @@ -127,8 +157,8 @@ func CrossingFunc(cur realm) {
_ = cur
}

func init() {
CrossingFunc(cross)
func Caller(cur realm) {
CrossingFunc(cross(cur))
}

`
Expand Down
Loading