-
Notifications
You must be signed in to change notification settings - Fork 243
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(go): add UnsafeCast function (#3316)
The `UnsafeCast` function can be used to forcefully convert from one type of jsii proxy value (including `interface{}`) to another jsii interface (i.e: a class or interface instance). If performs a "clean" cast when possible, and creates a new, aliased proxy otherwise. It is the user's responsibility to ensure they are passing the correct arguments into the function. Fixes #2819 --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
- Loading branch information
Romain Marcadier
committed
Jan 12, 2022
1 parent
ad6ce99
commit 19da85e
Showing
25 changed files
with
373 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ func New() abc.Cdk16625 { | |
return c | ||
} | ||
|
||
type cdk16625 struct{ | ||
type cdk16625 struct { | ||
abc.Cdk16625 | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
//go:build tools | ||
// +build tools | ||
|
||
// Package tools contains the necessary statements to ensure tool dependencies | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package jsii | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/aws/jsii-runtime-go/internal/kernel" | ||
) | ||
|
||
// UnsafeCast converts the given interface value to the desired target interface | ||
// pointer. Panics if the from value is not a jsii proxy object, or if the to | ||
// value is not a pointer to an interface type. | ||
func UnsafeCast(from interface{}, into interface{}) { | ||
rinto := reflect.ValueOf(into) | ||
if rinto.Kind() != reflect.Ptr { | ||
panic(fmt.Errorf("Second argument to UnsafeCast must be a pointer to an interface. Received %s", rinto.Type().String())) | ||
} | ||
rinto = rinto.Elem() | ||
if rinto.Kind() != reflect.Interface { | ||
panic(fmt.Errorf("Second argument to UnsafeCast must be a pointer to an interface. Received pointer to %s", rinto.Type().String())) | ||
} | ||
|
||
rfrom := reflect.ValueOf(from) | ||
|
||
// If rfrom is essentially nil, set into to nil and return. | ||
if !rfrom.IsValid() || rfrom.IsZero() { | ||
null := reflect.Zero(rinto.Type()) | ||
rinto.Set(null) | ||
return | ||
} | ||
// Interfaces may present as a pointer to an implementing struct, and that's fine... | ||
if rfrom.Kind() != reflect.Interface && rfrom.Kind() != reflect.Ptr { | ||
panic(fmt.Errorf("First argument to UnsafeCast must be an interface value. Received %s", rfrom.Type().String())) | ||
} | ||
|
||
// If rfrom can be directly converted to rinto, just do it. | ||
if rfrom.CanConvert(rinto.Type()) { | ||
rfrom = rfrom.Convert(rinto.Type()) | ||
rinto.Set(rfrom) | ||
return | ||
} | ||
|
||
client := kernel.GetClient() | ||
if objID, found := client.FindObjectRef(rfrom); found { | ||
// Ensures the value is initialized properly. Panics if the target value is not a jsii interface type. | ||
client.Types().InitJsiiProxy(rinto) | ||
|
||
// If the target type is a behavioral interface, add it to the ObjectRef.Interfaces list. | ||
if fqn, found := client.Types().InterfaceFQN(rinto.Type()); found { | ||
objID.Interfaces = append(objID.Interfaces, fqn) | ||
} | ||
|
||
// Make the new value an alias to the old value. | ||
client.RegisterInstance(rinto, objID) | ||
return | ||
} | ||
|
||
panic(fmt.Errorf("First argument to UnsafeCast must be a jsii proxy value. Received %s", rfrom.String())) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package jsii | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/aws/jsii-runtime-go/internal/api" | ||
"github.com/aws/jsii-runtime-go/internal/kernel" | ||
) | ||
|
||
type MockInterfaceABase interface { | ||
MockMethodABase(_ float64) | ||
} | ||
|
||
type mockABase struct { | ||
_ int // padding | ||
} | ||
|
||
func (m *mockABase) MockMethodABase(_ float64) {} | ||
|
||
type MockInterfaceA interface { | ||
MockInterfaceABase | ||
MockMethodA(_ string) | ||
} | ||
|
||
func NewMockInterfaceA() MockInterfaceA { | ||
return &mockA{mockABase{}} | ||
} | ||
|
||
type mockA struct { | ||
mockABase | ||
} | ||
|
||
func (m *mockA) MockMethodA(_ string) {} | ||
|
||
type MockInterfaceB interface { | ||
MockMethodB(_ int) | ||
} | ||
|
||
func NewMockInterfaceB() MockInterfaceB { | ||
return &mockB{} | ||
} | ||
|
||
type mockB struct { | ||
_ int // Padding | ||
} | ||
|
||
func (m *mockB) MockMethodB(_ int) {} | ||
|
||
func TestNilSource(t *testing.T) { | ||
// Make "into" not nil to ensure the cast function overwrites it. | ||
into := NewMockInterfaceB() | ||
UnsafeCast(nil, &into) | ||
|
||
if into != nil { | ||
t.Fail() | ||
} | ||
} | ||
|
||
func TestSourceAndTargetAreTheSame(t *testing.T) { | ||
into := NewMockInterfaceB() | ||
original := into | ||
UnsafeCast(into, &into) | ||
|
||
if into != original { | ||
t.Fail() | ||
} | ||
} | ||
|
||
func TestTargetIsSubclassOfSource(t *testing.T) { | ||
from := NewMockInterfaceA() | ||
var into MockInterfaceABase | ||
UnsafeCast(from, &into) | ||
|
||
if into != from { | ||
t.Fail() | ||
} | ||
} | ||
|
||
func TestRegistersAlias(t *testing.T) { | ||
client := kernel.GetClient() | ||
|
||
objid := api.ObjectRef{InstanceID: "Object@1337#42"} | ||
from := NewMockInterfaceA() | ||
client.RegisterInstance(reflect.ValueOf(from), objid) | ||
|
||
var into MockInterfaceB | ||
client.Types().RegisterInterface(api.FQN("mock.InterfaceB"), reflect.TypeOf(&into).Elem(), []api.Override{}, func() interface{} { return NewMockInterfaceB() }) | ||
|
||
UnsafeCast(from, &into) | ||
|
||
if into == nil { | ||
t.Fail() | ||
} | ||
|
||
if refid, found := client.FindObjectRef(reflect.ValueOf(into)); !found { | ||
t.Fail() | ||
} else if refid.InstanceID != objid.InstanceID { | ||
t.Fail() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,16 @@ | ||
module github.com/aws/jsii-runtime-go | ||
|
||
go 1.16 | ||
go 1.17 | ||
|
||
require ( | ||
github.com/Masterminds/semver/v3 v3.1.1 | ||
github.com/stretchr/testify v1.7.0 | ||
) | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect | ||
) | ||
|
||
retract v1.27.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= | ||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= | ||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
Oops, something went wrong.