Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Avoid double implicit type conversion on deferred built-in arguments. #998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2021
Merged
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
13 changes: 11 additions & 2 deletions compiler/statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,23 @@ func (c *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) {
isJs = typesutil.IsJsPackage(c.p.Uses[fun.Sel].Pkg())
}
sig := c.p.TypeOf(s.Call.Fun).Underlying().(*types.Signature)
sigTypes := signatureTypes{Sig: sig}
args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid())
if isBuiltin || isJs {
// Since some builtins or js.Object methods may not transpile into
// callable expressions, we need to wrap then in a proxy lambda in order
// to push them onto the deferral stack.
vars := make([]string, len(s.Call.Args))
callArgs := make([]ast.Expr, len(s.Call.Args))
for i, arg := range s.Call.Args {
ellipsis := s.Call.Ellipsis

for i := range s.Call.Args {
v := c.newVariable("_arg")
vars[i] = v
callArgs[i] = c.newIdent(v, c.p.TypeOf(arg))
// Subtle: the proxy lambda argument needs to be assigned with the type
// that the original function expects, and not with the argument
// expression result type, or we may do implicit type conversion twice.
callArgs[i] = c.newIdent(v, sigTypes.Param(i, ellipsis.IsValid()))
}
call := c.translateExpr(&ast.CallExpr{
Fun: s.Call.Fun,
Expand Down
74 changes: 57 additions & 17 deletions compiler/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,7 @@ func (c *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, e
}
}

paramsLen := sig.Params().Len()

var varargType *types.Slice
if sig.Variadic() && !ellipsis {
varargType = sig.Params().At(paramsLen - 1).Type().(*types.Slice)
}
sigTypes := signatureTypes{Sig: sig}

preserveOrder := false
for i := 1; i < len(argExprs); i++ {
Expand All @@ -102,15 +97,7 @@ func (c *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, e

args := make([]string, len(argExprs))
for i, argExpr := range argExprs {
var argType types.Type
switch {
case varargType != nil && i >= paramsLen-1:
argType = varargType.Elem()
default:
argType = sig.Params().At(i).Type()
}

arg := c.translateImplicitConversionWithCloning(argExpr, argType).String()
arg := c.translateImplicitConversionWithCloning(argExpr, sigTypes.Param(i, ellipsis)).String()

if preserveOrder && c.p.Types[argExpr].Value == nil {
argVar := c.newVariable("_arg")
Expand All @@ -121,8 +108,11 @@ func (c *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, e
args[i] = arg
}

if varargType != nil {
return append(args[:paramsLen-1], fmt.Sprintf("new %s([%s])", c.typeName(varargType), strings.Join(args[paramsLen-1:], ", ")))
// If variadic arguments were passed in as individual elements, regroup them
// into a slice and pass it as a single argument.
if sig.Variadic() && !ellipsis {
return append(args[:sigTypes.RequiredParams()],
fmt.Sprintf("new %s([%s])", c.typeName(sigTypes.VariadicType()), strings.Join(args[sigTypes.RequiredParams():], ", ")))
}
return args
}
Expand Down Expand Up @@ -671,3 +661,53 @@ func formatJSStructTagVal(jsTag string) string {
// Safe to use dot notation without any escaping.
return "." + jsTag
}

// signatureTypes is a helper that provides convenient access to function
// signature type information.
type signatureTypes struct {
Sig *types.Signature
}

// RequiredParams returns the number of required parameters in the function signature.
func (st signatureTypes) RequiredParams() int {
l := st.Sig.Params().Len()
if st.Sig.Variadic() {
return l - 1 // Last parameter is a slice of variadic params.
}
return l
}

// VariadicType returns the slice-type corresponding to the signature's variadic
// parameter, or nil of the signature is not variadic. With the exception of
// the special-case `append([]byte{}, "string"...)`, the returned type is
// `*types.Slice` and `.Elem()` method can be used to get the type of individual
// arguments.
func (st signatureTypes) VariadicType() types.Type {
if !st.Sig.Variadic() {
return nil
}
return st.Sig.Params().At(st.Sig.Params().Len() - 1).Type()
}

// Returns the expected argument type for the i'th argument position.
//
// This function is able to return correct expected types for variadic calls
// both when ellipsis syntax (e.g. myFunc(requiredArg, optionalArgSlice...))
// is used and when optional args are passed individually.
//
// The returned types may differ from the actual argument expression types if
// there is an implicit type conversion involved (e.g. passing a struct into a
// function that expects an interface).
func (st signatureTypes) Param(i int, ellipsis bool) types.Type {
if i < st.RequiredParams() {
return st.Sig.Params().At(i).Type()
}
if !st.Sig.Variadic() {
// This should never happen if the code was type-checked successfully.
panic(fmt.Errorf("Tried to access parameter %d of a non-variadic signature %s", i, st.Sig))
}
if ellipsis {
return st.VariadicType()
}
return st.VariadicType().(*types.Slice).Elem()
}
11 changes: 11 additions & 0 deletions tests/lowlevel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ func TestTimeInternalizationExternalization(t *testing.T) {
t.Fatalf("got != want:\ngot:\n%s\nwant:\n%s", got, want)
}
}

func TestDeferBuiltin(t *testing.T) {
if runtime.GOARCH == "js" {
t.Skip("test meant to be run using normal Go compiler (needs os/exec)")
}

got, err := exec.Command("gopherjs", "run", filepath.Join("testdata", "defer_builtin.go")).CombinedOutput()
if err != nil {
t.Fatalf("%v:\n%s", err, got)
}
}
22 changes: 22 additions & 0 deletions tests/testdata/defer_builtin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

type set map[interface{}]struct{}
type key struct{ a int }

var m = set{}

func deferredDelete(k key) {
// This built-in deferral will transpile into a "delete" statement wrapped
// into a proxy lambda. This test ensures we correctly assign proxy lambda
// argument types.
defer delete(m, k)
}

func main() {
k := key{a: 42}
m[k] = struct{}{}
deferredDelete(k)
if _, found := m[k]; found {
panic("deferred delete didn't work!")
}
}