| package compiler |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "go/constant" |
| "go/token" |
| "go/types" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "github.com/gopherjs/gopherjs/compiler/analysis" |
| "github.com/gopherjs/gopherjs/compiler/astutil" |
| "github.com/gopherjs/gopherjs/compiler/internal/typeparams" |
| "github.com/gopherjs/gopherjs/compiler/typesutil" |
| ) |
| |
| type expression struct { |
| str string |
| parens bool |
| } |
| |
| func (e *expression) String() string { |
| return e.str |
| } |
| |
| func (e *expression) StringWithParens() string { |
| if e.parens { |
| return "(" + e.str + ")" |
| } |
| return e.str |
| } |
| |
| func (fc *funcContext) translateExpr(expr ast.Expr) *expression { |
| exprType := fc.typeOf(expr) |
| if value := fc.pkgCtx.Types[expr].Value; value != nil { |
| basic := exprType.Underlying().(*types.Basic) |
| switch { |
| case isBoolean(basic): |
| return fc.formatExpr("%s", strconv.FormatBool(constant.BoolVal(value))) |
| case isInteger(basic): |
| if is64Bit(basic) { |
| if basic.Kind() == types.Int64 { |
| d, ok := constant.Int64Val(constant.ToInt(value)) |
| if !ok { |
| panic("could not get exact uint") |
| } |
| return fc.formatExpr("new %s(%s, %s)", fc.typeName(exprType), strconv.FormatInt(d>>32, 10), strconv.FormatUint(uint64(d)&(1<<32-1), 10)) |
| } |
| d, ok := constant.Uint64Val(constant.ToInt(value)) |
| if !ok { |
| panic("could not get exact uint") |
| } |
| return fc.formatExpr("new %s(%s, %s)", fc.typeName(exprType), strconv.FormatUint(d>>32, 10), strconv.FormatUint(d&(1<<32-1), 10)) |
| } |
| d, ok := constant.Int64Val(constant.ToInt(value)) |
| if !ok { |
| panic("could not get exact int") |
| } |
| return fc.formatExpr("%s", strconv.FormatInt(d, 10)) |
| case isFloat(basic): |
| f, _ := constant.Float64Val(value) |
| return fc.formatExpr("%s", strconv.FormatFloat(f, 'g', -1, 64)) |
| case isComplex(basic): |
| r, _ := constant.Float64Val(constant.Real(value)) |
| i, _ := constant.Float64Val(constant.Imag(value)) |
| if basic.Kind() == types.UntypedComplex { |
| exprType = types.Typ[types.Complex128] |
| } |
| return fc.formatExpr("new %s(%s, %s)", fc.typeName(exprType), strconv.FormatFloat(r, 'g', -1, 64), strconv.FormatFloat(i, 'g', -1, 64)) |
| case isString(basic): |
| return fc.formatExpr("%s", encodeString(constant.StringVal(value))) |
| default: |
| panic("Unhandled constant type: " + basic.String()) |
| } |
| } |
| |
| var inst typeparams.Instance |
| switch e := expr.(type) { |
| case *ast.SelectorExpr: |
| inst = fc.instanceOf(e.Sel) |
| case *ast.Ident: |
| inst = fc.instanceOf(e) |
| } |
| |
| if inst.Object != nil && typesutil.IsJsPackage(inst.Object.Pkg()) { |
| switch inst.Object.Name() { |
| case "Global": |
| return fc.formatExpr("$global") |
| case "Module": |
| return fc.formatExpr("$module") |
| case "Undefined": |
| return fc.formatExpr("undefined") |
| } |
| } |
| |
| switch e := expr.(type) { |
| case *ast.CompositeLit: |
| if ptrType, isPointer := exprType.Underlying().(*types.Pointer); isPointer { |
| // Go automatically treats `[]*T{{}}` as `[]*T{&T{}}`, in which case the |
| // inner composite literal `{}` would has a pointer type. To make sure the |
| // type conversion is handled correctly, we generate the explicit AST for |
| // this. |
| var rewritten ast.Expr = fc.setType(&ast.UnaryExpr{ |
| OpPos: e.Pos(), |
| Op: token.AND, |
| X: fc.setType(&ast.CompositeLit{ |
| Elts: e.Elts, |
| }, ptrType.Elem()), |
| }, ptrType) |
| |
| if exprType, ok := exprType.(*types.Named); ok { |
| // Handle a special case when the pointer type is named, e.g.: |
| // type PS *S |
| // _ = []PS{{}} |
| // In that case the value corresponding to the inner literal `{}` is |
| // initialized as `&S{}` and then converted to `PS`: `[]PS{PS(&S{})}`. |
| typeCast := fc.setType(&ast.CallExpr{ |
| Fun: fc.newTypeIdent(exprType.String(), exprType.Obj()), |
| Lparen: e.Lbrace, |
| Args: []ast.Expr{rewritten}, |
| Rparen: e.Rbrace, |
| }, exprType) |
| rewritten = typeCast |
| } |
| return fc.translateExpr(rewritten) |
| } |
| |
| collectIndexedElements := func(elementType types.Type) []string { |
| var elements []string |
| i := 0 |
| zero := fc.translateExpr(fc.zeroValue(elementType)).String() |
| for _, element := range e.Elts { |
| if kve, isKve := element.(*ast.KeyValueExpr); isKve { |
| key, ok := constant.Int64Val(constant.ToInt(fc.pkgCtx.Types[kve.Key].Value)) |
| if !ok { |
| panic("could not get exact int") |
| } |
| i = int(key) |
| element = kve.Value |
| } |
| for len(elements) <= i { |
| elements = append(elements, zero) |
| } |
| elements[i] = fc.translateImplicitConversionWithCloning(element, elementType).String() |
| i++ |
| } |
| return elements |
| } |
| |
| switch t := exprType.Underlying().(type) { |
| case *types.Array: |
| elements := collectIndexedElements(t.Elem()) |
| if len(elements) == 0 { |
| return fc.formatExpr("%s.zero()", fc.typeName(t)) |
| } |
| zero := fc.translateExpr(fc.zeroValue(t.Elem())).String() |
| for len(elements) < int(t.Len()) { |
| elements = append(elements, zero) |
| } |
| return fc.formatExpr(`$toNativeArray(%s, [%s])`, typeKind(t.Elem()), strings.Join(elements, ", ")) |
| case *types.Slice: |
| return fc.formatExpr("new %s([%s])", fc.typeName(exprType), strings.Join(collectIndexedElements(t.Elem()), ", ")) |
| case *types.Map: |
| entries := make([]string, len(e.Elts)) |
| for i, element := range e.Elts { |
| kve := element.(*ast.KeyValueExpr) |
| entries[i] = fmt.Sprintf("{ k: %s, v: %s }", fc.translateImplicitConversionWithCloning(kve.Key, t.Key()), fc.translateImplicitConversionWithCloning(kve.Value, t.Elem())) |
| } |
| return fc.formatExpr("$makeMap(%s.keyFor, [%s])", fc.typeName(t.Key()), strings.Join(entries, ", ")) |
| case *types.Struct: |
| elements := make([]string, t.NumFields()) |
| isKeyValue := true |
| if len(e.Elts) != 0 { |
| _, isKeyValue = e.Elts[0].(*ast.KeyValueExpr) |
| } |
| if !isKeyValue { |
| for i, element := range e.Elts { |
| elements[i] = fc.translateImplicitConversionWithCloning(element, t.Field(i).Type()).String() |
| } |
| } |
| if isKeyValue { |
| for i := range elements { |
| elements[i] = fc.translateExpr(fc.zeroValue(t.Field(i).Type())).String() |
| } |
| for _, element := range e.Elts { |
| kve := element.(*ast.KeyValueExpr) |
| for j := range elements { |
| if kve.Key.(*ast.Ident).Name == t.Field(j).Name() { |
| elements[j] = fc.translateImplicitConversionWithCloning(kve.Value, t.Field(j).Type()).String() |
| break |
| } |
| } |
| } |
| } |
| return fc.formatExpr("new %s.ptr(%s)", fc.typeName(exprType), strings.Join(elements, ", ")) |
| default: |
| panic(fmt.Sprintf("Unhandled CompositeLit type: %[1]T %[1]v\n", t)) |
| } |
| |
| case *ast.FuncLit: |
| _, fun := translateFunction(e.Type, nil, e.Body, fc, exprType.(*types.Signature), fc.pkgCtx.FuncLitInfos[e], "", typeparams.Instance{}) |
| if len(fc.pkgCtx.escapingVars) != 0 { |
| names := make([]string, 0, len(fc.pkgCtx.escapingVars)) |
| for obj := range fc.pkgCtx.escapingVars { |
| name, ok := fc.assignedObjectName(obj) |
| if !ok { |
| // This should never happen. |
| panic(fmt.Errorf("escaping variable %s hasn't been assigned a JS name", obj)) |
| } |
| names = append(names, name) |
| } |
| sort.Strings(names) |
| list := strings.Join(names, ", ") |
| return fc.formatExpr("(function(%s) { return %s; })(%s)", list, fun, list) |
| } |
| return fc.formatExpr("(%s)", fun) |
| |
| case *ast.UnaryExpr: |
| t := fc.typeOf(e.X) |
| switch e.Op { |
| case token.AND: |
| if typesutil.IsJsObject(exprType) { |
| return fc.formatExpr("%e.object", e.X) |
| } |
| |
| switch t.Underlying().(type) { |
| case *types.Struct, *types.Array: |
| // JavaScript's pass-by-reference semantics makes passing array's or |
| // struct's object semantically equivalent to passing a pointer |
| // TODO(nevkontakte): Evaluate if performance gain justifies complexity |
| // introduced by the special case. |
| return fc.translateExpr(e.X) |
| } |
| |
| elemType := exprType.(*types.Pointer).Elem() |
| |
| switch x := astutil.RemoveParens(e.X).(type) { |
| case *ast.CompositeLit: |
| return fc.formatExpr("$newDataPointer(%e, %s)", x, fc.typeName(fc.typeOf(e))) |
| case *ast.Ident: |
| obj := fc.pkgCtx.Uses[x].(*types.Var) |
| if fc.pkgCtx.escapingVars[obj] { |
| name, ok := fc.assignedObjectName(obj) |
| if !ok { |
| // This should never happen. |
| panic(fmt.Errorf("escaping variable %s hasn't been assigned a JS name", obj)) |
| } |
| return fc.formatExpr("(%1s.$ptr || (%1s.$ptr = new %2s(function() { return this.$target[0]; }, function($v) { this.$target[0] = $v; }, %1s)))", name, fc.typeName(exprType)) |
| } |
| return fc.formatExpr(`(%1s || (%1s = new %2s(function() { return %3s; }, function($v) { %4s })))`, fc.varPtrName(obj), fc.typeName(exprType), fc.objectName(obj), fc.translateAssign(x, fc.newIdent("$v", elemType), false)) |
| case *ast.SelectorExpr: |
| sel, ok := fc.selectionOf(x) |
| if !ok { |
| // qualified identifier |
| obj := fc.pkgCtx.Uses[x.Sel].(*types.Var) |
| return fc.formatExpr(`(%1s || (%1s = new %2s(function() { return %3s; }, function($v) { %4s })))`, fc.varPtrName(obj), fc.typeName(exprType), fc.objectName(obj), fc.translateAssign(x, fc.newIdent("$v", elemType), false)) |
| } |
| newSel := &ast.SelectorExpr{X: fc.newIdent("this.$target", fc.typeOf(x.X)), Sel: x.Sel} |
| fc.setType(newSel, exprType) |
| fc.pkgCtx.additionalSelections[newSel] = sel |
| return fc.formatExpr("(%1e.$ptr_%2s || (%1e.$ptr_%2s = new %3s(function() { return %4e; }, function($v) { %5s }, %1e)))", x.X, x.Sel.Name, fc.typeName(exprType), newSel, fc.translateAssign(newSel, fc.newIdent("$v", exprType), false)) |
| case *ast.IndexExpr: |
| if _, ok := fc.typeOf(x.X).Underlying().(*types.Slice); ok { |
| return fc.formatExpr("$indexPtr(%1e.$array, %1e.$offset + %2e, %3s)", x.X, x.Index, fc.typeName(exprType)) |
| } |
| return fc.formatExpr("$indexPtr(%e, %e, %s)", x.X, x.Index, fc.typeName(exprType)) |
| case *ast.StarExpr: |
| return fc.translateExpr(x.X) |
| default: |
| panic(fmt.Sprintf("Unhandled: %T\n", x)) |
| } |
| |
| case token.ARROW: |
| call := &ast.CallExpr{ |
| Fun: fc.newIdent("$recv", types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), |
| Args: []ast.Expr{e.X}, |
| } |
| fc.Blocking[call] = true |
| if _, isTuple := exprType.(*types.Tuple); isTuple { |
| return fc.formatExpr("%e", call) |
| } |
| return fc.formatExpr("%e[0]", call) |
| } |
| |
| basic := t.Underlying().(*types.Basic) |
| switch e.Op { |
| case token.ADD: |
| return fc.translateExpr(e.X) |
| case token.SUB: |
| switch { |
| case is64Bit(basic): |
| return fc.formatExpr("new %1s(-%2h, -%2l)", fc.typeName(t), e.X) |
| case isComplex(basic): |
| return fc.formatExpr("new %1s(-%2r, -%2i)", fc.typeName(t), e.X) |
| case isUnsigned(basic): |
| return fc.fixNumber(fc.formatExpr("-%e", e.X), basic) |
| default: |
| return fc.formatExpr("-%e", e.X) |
| } |
| case token.XOR: |
| if is64Bit(basic) { |
| return fc.formatExpr("new %1s(~%2h, ~%2l >>> 0)", fc.typeName(t), e.X) |
| } |
| return fc.fixNumber(fc.formatExpr("~%e", e.X), basic) |
| case token.NOT: |
| return fc.formatExpr("!%e", e.X) |
| default: |
| panic(e.Op) |
| } |
| |
| case *ast.BinaryExpr: |
| if e.Op == token.NEQ { |
| return fc.formatExpr("!(%s)", fc.translateExpr(&ast.BinaryExpr{ |
| X: e.X, |
| Op: token.EQL, |
| Y: e.Y, |
| })) |
| } |
| |
| t := fc.typeOf(e.X) |
| t2 := fc.typeOf(e.Y) |
| _, isInterface := t2.Underlying().(*types.Interface) |
| if isInterface || types.Identical(t, types.Typ[types.UntypedNil]) { |
| t = t2 |
| } |
| |
| if basic, isBasic := t.Underlying().(*types.Basic); isBasic && isNumeric(basic) { |
| if is64Bit(basic) { |
| switch e.Op { |
| case token.MUL: |
| return fc.formatExpr("$mul64(%e, %e)", e.X, e.Y) |
| case token.QUO: |
| return fc.formatExpr("$div64(%e, %e, false)", e.X, e.Y) |
| case token.REM: |
| return fc.formatExpr("$div64(%e, %e, true)", e.X, e.Y) |
| case token.SHL: |
| return fc.formatExpr("$shiftLeft64(%e, %f)", e.X, e.Y) |
| case token.SHR: |
| return fc.formatExpr("$shiftRight%s(%e, %f)", toJavaScriptType(basic), e.X, e.Y) |
| case token.EQL: |
| return fc.formatExpr("(%1h === %2h && %1l === %2l)", e.X, e.Y) |
| case token.LSS: |
| return fc.formatExpr("(%1h < %2h || (%1h === %2h && %1l < %2l))", e.X, e.Y) |
| case token.LEQ: |
| return fc.formatExpr("(%1h < %2h || (%1h === %2h && %1l <= %2l))", e.X, e.Y) |
| case token.GTR: |
| return fc.formatExpr("(%1h > %2h || (%1h === %2h && %1l > %2l))", e.X, e.Y) |
| case token.GEQ: |
| return fc.formatExpr("(%1h > %2h || (%1h === %2h && %1l >= %2l))", e.X, e.Y) |
| case token.ADD, token.SUB: |
| return fc.formatExpr("new %3s(%1h %4t %2h, %1l %4t %2l)", e.X, e.Y, fc.typeName(t), e.Op) |
| case token.AND, token.OR, token.XOR: |
| return fc.formatExpr("new %3s(%1h %4t %2h, (%1l %4t %2l) >>> 0)", e.X, e.Y, fc.typeName(t), e.Op) |
| case token.AND_NOT: |
| return fc.formatExpr("new %3s(%1h & ~%2h, (%1l & ~%2l) >>> 0)", e.X, e.Y, fc.typeName(t)) |
| default: |
| panic(e.Op) |
| } |
| } |
| |
| if isComplex(basic) { |
| switch e.Op { |
| case token.EQL: |
| return fc.formatExpr("(%1r === %2r && %1i === %2i)", e.X, e.Y) |
| case token.ADD, token.SUB: |
| return fc.formatExpr("new %3s(%1r %4t %2r, %1i %4t %2i)", e.X, e.Y, fc.typeName(t), e.Op) |
| case token.MUL: |
| return fc.formatExpr("new %3s(%1r * %2r - %1i * %2i, %1r * %2i + %1i * %2r)", e.X, e.Y, fc.typeName(t)) |
| case token.QUO: |
| return fc.formatExpr("$divComplex(%e, %e)", e.X, e.Y) |
| default: |
| panic(e.Op) |
| } |
| } |
| |
| switch e.Op { |
| case token.EQL: |
| return fc.formatParenExpr("%e === %e", e.X, e.Y) |
| case token.LSS, token.LEQ, token.GTR, token.GEQ: |
| return fc.formatExpr("%e %t %e", e.X, e.Op, e.Y) |
| case token.ADD, token.SUB: |
| return fc.fixNumber(fc.formatExpr("%e %t %e", e.X, e.Op, e.Y), basic) |
| case token.MUL: |
| switch basic.Kind() { |
| case types.Int32, types.Int: |
| return fc.formatParenExpr("$imul(%e, %e)", e.X, e.Y) |
| case types.Uint32, types.Uintptr: |
| return fc.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) |
| } |
| return fc.fixNumber(fc.formatExpr("%e * %e", e.X, e.Y), basic) |
| case token.QUO: |
| if isInteger(basic) { |
| // cut off decimals |
| shift := ">>" |
| if isUnsigned(basic) { |
| shift = ">>>" |
| } |
| return fc.formatExpr(`(%1s = %2e / %3e, (%1s === %1s && %1s !== 1/0 && %1s !== -1/0) ? %1s %4s 0 : $throwRuntimeError("integer divide by zero"))`, fc.newLocalVariable("_q"), e.X, e.Y, shift) |
| } |
| if basic.Kind() == types.Float32 { |
| return fc.fixNumber(fc.formatExpr("%e / %e", e.X, e.Y), basic) |
| } |
| return fc.formatExpr("%e / %e", e.X, e.Y) |
| case token.REM: |
| return fc.formatExpr(`(%1s = %2e %% %3e, %1s === %1s ? %1s : $throwRuntimeError("integer divide by zero"))`, fc.newLocalVariable("_r"), e.X, e.Y) |
| case token.SHL, token.SHR: |
| op := e.Op.String() |
| if e.Op == token.SHR && isUnsigned(basic) { |
| op = ">>>" |
| } |
| if v := fc.pkgCtx.Types[e.Y].Value; v != nil { |
| i, _ := constant.Uint64Val(constant.ToInt(v)) |
| if i >= 32 { |
| return fc.formatExpr("0") |
| } |
| return fc.fixNumber(fc.formatExpr("%e %s %s", e.X, op, strconv.FormatUint(i, 10)), basic) |
| } |
| if e.Op == token.SHR && !isUnsigned(basic) { |
| return fc.fixNumber(fc.formatParenExpr("%e >> $min(%f, 31)", e.X, e.Y), basic) |
| } |
| y := fc.newLocalVariable("y") |
| return fc.fixNumber(fc.formatExpr("(%s = %f, %s < 32 ? (%e %s %s) : 0)", y, e.Y, y, e.X, op, y), basic) |
| case token.AND, token.OR: |
| if isUnsigned(basic) { |
| return fc.formatParenExpr("(%e %t %e) >>> 0", e.X, e.Op, e.Y) |
| } |
| return fc.formatParenExpr("%e %t %e", e.X, e.Op, e.Y) |
| case token.AND_NOT: |
| return fc.fixNumber(fc.formatParenExpr("%e & ~%e", e.X, e.Y), basic) |
| case token.XOR: |
| return fc.fixNumber(fc.formatParenExpr("%e ^ %e", e.X, e.Y), basic) |
| default: |
| panic(e.Op) |
| } |
| } |
| |
| switch e.Op { |
| case token.ADD, token.LSS, token.LEQ, token.GTR, token.GEQ: |
| return fc.formatExpr("%e %t %e", e.X, e.Op, e.Y) |
| case token.LAND: |
| if fc.Blocking[e.Y] { |
| skipCase := fc.caseCounter |
| fc.caseCounter++ |
| resultVar := fc.newLocalVariable("_v") |
| fc.Printf("if (!(%s)) { %s = false; $s = %d; continue s; }", fc.translateExpr(e.X), resultVar, skipCase) |
| fc.Printf("%s = %s; case %d:", resultVar, fc.translateExpr(e.Y), skipCase) |
| return fc.formatExpr("%s", resultVar) |
| } |
| return fc.formatExpr("%e && %e", e.X, e.Y) |
| case token.LOR: |
| if fc.Blocking[e.Y] { |
| skipCase := fc.caseCounter |
| fc.caseCounter++ |
| resultVar := fc.newLocalVariable("_v") |
| fc.Printf("if (%s) { %s = true; $s = %d; continue s; }", fc.translateExpr(e.X), resultVar, skipCase) |
| fc.Printf("%s = %s; case %d:", resultVar, fc.translateExpr(e.Y), skipCase) |
| return fc.formatExpr("%s", resultVar) |
| } |
| return fc.formatExpr("%e || %e", e.X, e.Y) |
| case token.EQL: |
| switch u := t.Underlying().(type) { |
| case *types.Array, *types.Struct: |
| return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t)) |
| case *types.Interface: |
| return fc.formatExpr("$interfaceIsEqual(%s, %s)", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t)) |
| case *types.Basic: |
| if isBoolean(u) { |
| if b, ok := analysis.BoolValue(e.X, fc.pkgCtx.Info.Info); ok && b { |
| return fc.translateExpr(e.Y) |
| } |
| if b, ok := analysis.BoolValue(e.Y, fc.pkgCtx.Info.Info); ok && b { |
| return fc.translateExpr(e.X) |
| } |
| } |
| } |
| return fc.formatExpr("%s === %s", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t)) |
| default: |
| panic(e.Op) |
| } |
| |
| case *ast.ParenExpr: |
| return fc.formatParenExpr("%e", e.X) |
| |
| case *ast.IndexExpr: |
| switch t := fc.typeOf(e.X).Underlying().(type) { |
| case *types.Pointer: |
| if _, ok := t.Elem().Underlying().(*types.Array); !ok { |
| // Should never happen in type-checked code. |
| panic(fmt.Errorf("non-array pointers can't be used with index expression")) |
| } |
| // Rewrite arrPtr[i] → (*arrPtr)[i] to concentrate array dereferencing |
| // logic in one place. |
| x := &ast.StarExpr{ |
| Star: e.X.Pos(), |
| X: e.X, |
| } |
| astutil.SetType(fc.pkgCtx.Info.Info, t.Elem(), x) |
| e.X = x |
| return fc.translateExpr(e) |
| case *types.Array: |
| pattern := rangeCheck("%1e[%2f]", fc.pkgCtx.Types[e.Index].Value != nil, true) |
| return fc.formatExpr(pattern, e.X, e.Index) |
| case *types.Slice: |
| return fc.formatExpr(rangeCheck("%1e.$array[%1e.$offset + %2f]", fc.pkgCtx.Types[e.Index].Value != nil, false), e.X, e.Index) |
| case *types.Map: |
| if typesutil.IsJsObject(fc.typeOf(e.Index)) { |
| fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: e.Index.Pos(), Msg: "cannot use js.Object as map key"}) |
| } |
| key := fmt.Sprintf("%s.keyFor(%s)", fc.typeName(t.Key()), fc.translateImplicitConversion(e.Index, t.Key())) |
| if _, isTuple := exprType.(*types.Tuple); isTuple { |
| return fc.formatExpr( |
| `(%1s = $mapIndex(%2e,%3s), %1s !== undefined ? [%1s.v, true] : [%4e, false])`, |
| fc.newLocalVariable("_entry"), |
| e.X, |
| key, |
| fc.zeroValue(t.Elem()), |
| ) |
| } |
| return fc.formatExpr( |
| `(%1s = $mapIndex(%2e,%3s), %1s !== undefined ? %1s.v : %4e)`, |
| fc.newLocalVariable("_entry"), |
| e.X, |
| key, |
| fc.zeroValue(t.Elem()), |
| ) |
| case *types.Basic: |
| return fc.formatExpr("%e.charCodeAt(%f)", e.X, e.Index) |
| case *types.Signature: |
| return fc.formatExpr("%s", fc.instName(fc.instanceOf(e.X.(*ast.Ident)))) |
| default: |
| panic(fmt.Errorf(`unhandled IndexExpr: %T`, t)) |
| } |
| case *ast.IndexListExpr: |
| switch t := fc.typeOf(e.X).Underlying().(type) { |
| case *types.Signature: |
| return fc.formatExpr("%s", fc.instName(fc.instanceOf(e.X.(*ast.Ident)))) |
| default: |
| panic(fmt.Errorf("unhandled IndexListExpr: %T", t)) |
| } |
| case *ast.SliceExpr: |
| if b, isBasic := fc.typeOf(e.X).Underlying().(*types.Basic); isBasic && isString(b) { |
| switch { |
| case e.Low == nil && e.High == nil: |
| return fc.translateExpr(e.X) |
| case e.Low == nil: |
| return fc.formatExpr("$substring(%e, 0, %f)", e.X, e.High) |
| case e.High == nil: |
| return fc.formatExpr("$substring(%e, %f)", e.X, e.Low) |
| default: |
| return fc.formatExpr("$substring(%e, %f, %f)", e.X, e.Low, e.High) |
| } |
| } |
| slice := fc.translateConversionToSlice(e.X, exprType) |
| switch { |
| case e.Low == nil && e.High == nil: |
| return fc.formatExpr("%s", slice) |
| case e.Low == nil: |
| if e.Max != nil { |
| return fc.formatExpr("$subslice(%s, 0, %f, %f)", slice, e.High, e.Max) |
| } |
| return fc.formatExpr("$subslice(%s, 0, %f)", slice, e.High) |
| case e.High == nil: |
| return fc.formatExpr("$subslice(%s, %f)", slice, e.Low) |
| default: |
| if e.Max != nil { |
| return fc.formatExpr("$subslice(%s, %f, %f, %f)", slice, e.Low, e.High, e.Max) |
| } |
| return fc.formatExpr("$subslice(%s, %f, %f)", slice, e.Low, e.High) |
| } |
| |
| case *ast.SelectorExpr: |
| sel, ok := fc.selectionOf(e) |
| if !ok { |
| // qualified identifier |
| return fc.formatExpr("%s", fc.instName(inst)) |
| } |
| |
| switch sel.Kind() { |
| case types.FieldVal: |
| fields, jsTag := fc.translateSelection(sel, e.Pos()) |
| if jsTag != "" { |
| if _, ok := sel.Type().(*types.Signature); ok { |
| return fc.formatExpr("$internalize(%1e.%2s%3s, %4s, %1e.%2s)", e.X, strings.Join(fields, "."), formatJSStructTagVal(jsTag), fc.typeName(sel.Type())) |
| } |
| return fc.internalize(fc.formatExpr("%e.%s%s", e.X, strings.Join(fields, "."), formatJSStructTagVal(jsTag)), sel.Type()) |
| } |
| return fc.formatExpr("%e.%s", e.X, strings.Join(fields, ".")) |
| case types.MethodVal: |
| return fc.formatExpr(`$methodVal(%s, "%s")`, fc.makeReceiver(e), sel.Obj().(*types.Func).Name()) |
| case types.MethodExpr: |
| if !sel.Obj().Exported() { |
| fc.pkgCtx.dependencies[sel.Obj()] = true |
| } |
| if _, ok := sel.Recv().Underlying().(*types.Interface); ok { |
| return fc.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) |
| } |
| return fc.formatExpr(`$methodExpr(%s, "%s")`, fc.typeName(sel.Recv()), sel.Obj().(*types.Func).Name()) |
| default: |
| panic(fmt.Sprintf("unexpected sel.Kind(): %T", sel.Kind())) |
| } |
| |
| case *ast.CallExpr: |
| plainFun := astutil.RemoveParens(e.Fun) |
| |
| if astutil.IsTypeExpr(plainFun, fc.pkgCtx.Info.Info) { |
| return fc.formatExpr("(%s)", fc.translateConversion(e.Args[0], fc.typeOf(plainFun))) |
| } |
| |
| sig := fc.typeOf(plainFun).Underlying().(*types.Signature) |
| |
| switch f := plainFun.(type) { |
| case *ast.Ident: |
| obj := fc.pkgCtx.Uses[f] |
| if o, ok := obj.(*types.Builtin); ok { |
| return fc.translateBuiltin(o.Name(), sig, e.Args, e.Ellipsis.IsValid()) |
| } |
| if typesutil.IsJsPackage(obj.Pkg()) && obj.Name() == "InternalObject" { |
| return fc.translateExpr(e.Args[0]) |
| } |
| return fc.translateCall(e, sig, fc.translateExpr(f)) |
| |
| case *ast.SelectorExpr: |
| sel, ok := fc.selectionOf(f) |
| if !ok { |
| // qualified identifier |
| obj := fc.pkgCtx.Uses[f.Sel] |
| if o, ok := obj.(*types.Builtin); ok { |
| return fc.translateBuiltin(o.Name(), sig, e.Args, e.Ellipsis.IsValid()) |
| } |
| if typesutil.IsJsPackage(obj.Pkg()) { |
| switch obj.Name() { |
| case "Debugger": |
| return fc.formatExpr("debugger") |
| case "InternalObject": |
| return fc.translateExpr(e.Args[0]) |
| } |
| } |
| return fc.translateCall(e, sig, fc.translateExpr(f)) |
| } |
| |
| externalizeExpr := func(e ast.Expr) string { |
| t := fc.typeOf(e) |
| if types.Identical(t, types.Typ[types.UntypedNil]) { |
| return "null" |
| } |
| return fc.externalize(fc.translateExpr(e).String(), t) |
| } |
| externalizeArgs := func(args []ast.Expr) string { |
| s := make([]string, len(args)) |
| for i, arg := range args { |
| s[i] = externalizeExpr(arg) |
| } |
| return strings.Join(s, ", ") |
| } |
| |
| switch sel.Kind() { |
| case types.MethodVal: |
| recv := fc.makeReceiver(f) |
| declaredFuncRecv := sel.Obj().(*types.Func).Type().(*types.Signature).Recv().Type() |
| if typesutil.IsJsObject(declaredFuncRecv) { |
| globalRef := func(id string) string { |
| if recv.String() == "$global" && id[0] == '$' && len(id) > 1 { |
| return id |
| } |
| return recv.String() + "." + id |
| } |
| switch sel.Obj().Name() { |
| case "Get": |
| if id, ok := fc.identifierConstant(e.Args[0]); ok { |
| return fc.formatExpr("%s", globalRef(id)) |
| } |
| return fc.formatExpr("%s[$externalize(%e, $String)]", recv, e.Args[0]) |
| case "Set": |
| if id, ok := fc.identifierConstant(e.Args[0]); ok { |
| return fc.formatExpr("%s = %s", globalRef(id), externalizeExpr(e.Args[1])) |
| } |
| return fc.formatExpr("%s[$externalize(%e, $String)] = %s", recv, e.Args[0], externalizeExpr(e.Args[1])) |
| case "Delete": |
| return fc.formatExpr("delete %s[$externalize(%e, $String)]", recv, e.Args[0]) |
| case "Length": |
| return fc.formatExpr("$parseInt(%s.length)", recv) |
| case "Index": |
| return fc.formatExpr("%s[%e]", recv, e.Args[0]) |
| case "SetIndex": |
| return fc.formatExpr("%s[%e] = %s", recv, e.Args[0], externalizeExpr(e.Args[1])) |
| case "Call": |
| if id, ok := fc.identifierConstant(e.Args[0]); ok { |
| if e.Ellipsis.IsValid() { |
| objVar := fc.newLocalVariable("obj") |
| return fc.formatExpr("(%s = %s, %s.%s.apply(%s, %s))", objVar, recv, objVar, id, objVar, externalizeExpr(e.Args[1])) |
| } |
| return fc.formatExpr("%s(%s)", globalRef(id), externalizeArgs(e.Args[1:])) |
| } |
| if e.Ellipsis.IsValid() { |
| objVar := fc.newLocalVariable("obj") |
| return fc.formatExpr("(%s = %s, %s[$externalize(%e, $String)].apply(%s, %s))", objVar, recv, objVar, e.Args[0], objVar, externalizeExpr(e.Args[1])) |
| } |
| return fc.formatExpr("%s[$externalize(%e, $String)](%s)", recv, e.Args[0], externalizeArgs(e.Args[1:])) |
| case "Invoke": |
| if e.Ellipsis.IsValid() { |
| return fc.formatExpr("%s.apply(undefined, %s)", recv, externalizeExpr(e.Args[0])) |
| } |
| return fc.formatExpr("%s(%s)", recv, externalizeArgs(e.Args)) |
| case "New": |
| if e.Ellipsis.IsValid() { |
| return fc.formatExpr("new ($global.Function.prototype.bind.apply(%s, [undefined].concat(%s)))", recv, externalizeExpr(e.Args[0])) |
| } |
| return fc.formatExpr("new (%s)(%s)", recv, externalizeArgs(e.Args)) |
| case "Bool": |
| return fc.internalize(recv, types.Typ[types.Bool]) |
| case "String": |
| return fc.internalize(recv, types.Typ[types.String]) |
| case "Int": |
| return fc.internalize(recv, types.Typ[types.Int]) |
| case "Int64": |
| return fc.internalize(recv, types.Typ[types.Int64]) |
| case "Uint64": |
| return fc.internalize(recv, types.Typ[types.Uint64]) |
| case "Float": |
| return fc.internalize(recv, types.Typ[types.Float64]) |
| case "Interface": |
| return fc.internalize(recv, types.NewInterfaceType(nil, nil)) |
| case "Unsafe": |
| return recv |
| default: |
| panic("Invalid js package object: " + sel.Obj().Name()) |
| } |
| } |
| |
| methodName := sel.Obj().Name() |
| if reservedKeywords[methodName] { |
| methodName += "$" |
| } |
| return fc.translateCall(e, sig, fc.formatExpr("%s.%s", recv, methodName)) |
| |
| case types.FieldVal: |
| fields, jsTag := fc.translateSelection(sel, f.Pos()) |
| if jsTag != "" { |
| call := fc.formatExpr("%e.%s%s(%s)", f.X, strings.Join(fields, "."), formatJSStructTagVal(jsTag), externalizeArgs(e.Args)) |
| switch sig.Results().Len() { |
| case 0: |
| return call |
| case 1: |
| return fc.internalize(call, sig.Results().At(0).Type()) |
| default: |
| fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: f.Pos(), Msg: "field with js tag can not have func type with multiple results"}) |
| } |
| } |
| return fc.translateCall(e, sig, fc.formatExpr("%e.%s", f.X, strings.Join(fields, "."))) |
| |
| case types.MethodExpr: |
| return fc.translateCall(e, sig, fc.translateExpr(f)) |
| |
| default: |
| panic(fmt.Sprintf("unexpected sel.Kind(): %T", sel.Kind())) |
| } |
| default: |
| return fc.translateCall(e, sig, fc.translateExpr(plainFun)) |
| } |
| |
| case *ast.StarExpr: |
| if typesutil.IsJsObject(fc.typeOf(e.X)) { |
| return fc.formatExpr("new $jsObjectPtr(%e)", e.X) |
| } |
| if c1, isCall := e.X.(*ast.CallExpr); isCall && len(c1.Args) == 1 { |
| if c2, isCall := c1.Args[0].(*ast.CallExpr); isCall && len(c2.Args) == 1 && types.Identical(fc.typeOf(c2.Fun), types.Typ[types.UnsafePointer]) { |
| if unary, isUnary := c2.Args[0].(*ast.UnaryExpr); isUnary && unary.Op == token.AND { |
| return fc.translateExpr(unary.X) // unsafe conversion |
| } |
| } |
| } |
| switch exprType.Underlying().(type) { |
| case *types.Struct, *types.Array: |
| return fc.translateExpr(e.X) |
| } |
| return fc.formatExpr("%e.$get()", e.X) |
| |
| case *ast.TypeAssertExpr: |
| if e.Type == nil { |
| return fc.translateExpr(e.X) |
| } |
| t := fc.typeOf(e.Type) |
| if _, isTuple := exprType.(*types.Tuple); isTuple { |
| return fc.formatExpr("$assertType(%e, %s, true)", e.X, fc.typeName(t)) |
| } |
| return fc.formatExpr("$assertType(%e, %s)", e.X, fc.typeName(t)) |
| |
| case *ast.Ident: |
| if e.Name == "_" { |
| panic("Tried to translate underscore identifier.") |
| } |
| switch o := inst.Object.(type) { |
| case *types.Var, *types.Const: |
| return fc.formatExpr("%s", fc.instName(inst)) |
| case *types.Func: |
| return fc.formatExpr("%s", fc.instName(inst)) |
| case *types.TypeName: |
| return fc.formatExpr("%s", fc.typeName(o.Type())) |
| case *types.Nil: |
| if typesutil.IsJsObject(exprType) { |
| return fc.formatExpr("null") |
| } |
| switch t := exprType.Underlying().(type) { |
| case *types.Basic: |
| if t.Kind() != types.UnsafePointer { |
| panic("unexpected basic type") |
| } |
| return fc.formatExpr("0") |
| case *types.Slice, *types.Pointer: |
| return fc.formatExpr("%s.nil", fc.typeName(exprType)) |
| case *types.Chan: |
| return fc.formatExpr("$chanNil") |
| case *types.Map: |
| return fc.formatExpr("false") |
| case *types.Interface: |
| return fc.formatExpr("$ifaceNil") |
| case *types.Signature: |
| return fc.formatExpr("$throwNilPointerError") |
| default: |
| panic(fmt.Sprintf("unexpected type: %T", t)) |
| } |
| default: |
| panic(fmt.Sprintf("Unhandled object: %T\n", o)) |
| } |
| |
| case nil: |
| return fc.formatExpr("") |
| |
| default: |
| panic(fmt.Sprintf("Unhandled expression: %T\n", e)) |
| |
| } |
| } |
| |
| func (fc *funcContext) translateCall(e *ast.CallExpr, sig *types.Signature, fun *expression) *expression { |
| args := fc.translateArgs(sig, e.Args, e.Ellipsis.IsValid()) |
| if fc.Blocking[e] { |
| resumeCase := fc.caseCounter |
| fc.caseCounter++ |
| returnVar := "$r" |
| if sig.Results().Len() != 0 { |
| returnVar = fc.newLocalVariable("_r") |
| } |
| fc.Printf("%[1]s = %[2]s(%[3]s); /* */ $s = %[4]d; case %[4]d: if($c) { $c = false; %[1]s = %[1]s.$blk(); } if (%[1]s && %[1]s.$blk !== undefined) { break s; }", returnVar, fun, strings.Join(args, ", "), resumeCase) |
| if sig.Results().Len() != 0 { |
| return fc.formatExpr("%s", returnVar) |
| } |
| return fc.formatExpr("") |
| } |
| return fc.formatExpr("%s(%s)", fun, strings.Join(args, ", ")) |
| } |
| |
| // delegatedCall returns a pair of JS expressions representing a callable function |
| // and its arguments to be invoked elsewhere. |
| // |
| // This function is necessary in conjunction with keywords such as `go` and `defer`, |
| // where we need to compute function and its arguments at the keyword site, |
| // but the call itself will happen elsewhere (hence "delegated"). |
| // |
| // Built-in functions and cetrain `js.Object` methods don't translate into JS |
| // function calls, and need to be wrapped before they can be delegated, which |
| // this function handles and returns JS expressions that are safe to delegate |
| // and behave like a regular JS function and a list of its argument values. |
| func (fc *funcContext) delegatedCall(expr *ast.CallExpr) (callable *expression, arglist *expression) { |
| isBuiltin := false |
| isJs := false |
| switch fun := expr.Fun.(type) { |
| case *ast.Ident: |
| _, isBuiltin = fc.pkgCtx.Uses[fun].(*types.Builtin) |
| case *ast.SelectorExpr: |
| isJs = typesutil.IsJsPackage(fc.pkgCtx.Uses[fun.Sel].Pkg()) |
| } |
| sig := typesutil.Signature{Sig: fc.typeOf(expr.Fun).Underlying().(*types.Signature)} |
| args := fc.translateArgs(sig.Sig, expr.Args, expr.Ellipsis.IsValid()) |
| |
| if !isBuiltin && !isJs { |
| // Normal function calls don't require wrappers. |
| callable = fc.translateExpr(expr.Fun) |
| arglist = fc.formatExpr("[%s]", strings.Join(args, ", ")) |
| return callable, arglist |
| } |
| |
| // 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(expr.Args)) |
| callArgs := make([]ast.Expr, len(expr.Args)) |
| ellipsis := expr.Ellipsis |
| |
| for i := range expr.Args { |
| v := fc.newLocalVariable("_arg") |
| vars[i] = v |
| // 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] = fc.newIdent(v, sig.Param(i, ellipsis.IsValid())) |
| } |
| wrapper := &ast.CallExpr{ |
| Fun: expr.Fun, |
| Args: callArgs, |
| Ellipsis: expr.Ellipsis, |
| } |
| callable = fc.formatExpr("function(%s) { %e; }", strings.Join(vars, ", "), wrapper) |
| arglist = fc.formatExpr("[%s]", strings.Join(args, ", ")) |
| return callable, arglist |
| } |
| |
| func (fc *funcContext) makeReceiver(e *ast.SelectorExpr) *expression { |
| sel, _ := fc.selectionOf(e) |
| if !sel.Obj().Exported() { |
| fc.pkgCtx.dependencies[sel.Obj()] = true |
| } |
| |
| x := e.X |
| recvType := sel.Recv() |
| if len(sel.Index()) > 1 { |
| for _, index := range sel.Index()[:len(sel.Index())-1] { |
| if ptr, isPtr := recvType.(*types.Pointer); isPtr { |
| recvType = ptr.Elem() |
| } |
| s := recvType.Underlying().(*types.Struct) |
| recvType = s.Field(index).Type() |
| } |
| |
| fakeSel := &ast.SelectorExpr{X: x, Sel: ast.NewIdent("o")} |
| fc.pkgCtx.additionalSelections[fakeSel] = typesutil.NewSelection(types.FieldVal, sel.Recv(), sel.Index()[:len(sel.Index())-1], nil, recvType) |
| x = fc.setType(fakeSel, recvType) |
| } |
| |
| _, isPointer := recvType.Underlying().(*types.Pointer) |
| methodsRecvType := sel.Obj().Type().(*types.Signature).Recv().Type() |
| _, pointerExpected := methodsRecvType.(*types.Pointer) |
| if !isPointer && pointerExpected { |
| recvType = types.NewPointer(recvType) |
| x = fc.setType(&ast.UnaryExpr{Op: token.AND, X: x}, recvType) |
| } |
| if isPointer && !pointerExpected { |
| x = fc.setType(x, methodsRecvType) |
| } |
| |
| recv := fc.translateImplicitConversionWithCloning(x, methodsRecvType) |
| if isWrapped(recvType) { |
| // Wrap JS-native value to have access to the Go type's methods. |
| recv = fc.formatExpr("new %s(%s)", fc.typeName(methodsRecvType), recv) |
| } |
| return recv |
| } |
| |
| func (fc *funcContext) translateBuiltin(name string, sig *types.Signature, args []ast.Expr, ellipsis bool) *expression { |
| switch name { |
| case "new": |
| t := sig.Results().At(0).Type().(*types.Pointer) |
| if fc.pkgCtx.Pkg.Path() == "syscall" && types.Identical(t.Elem().Underlying(), types.Typ[types.Uintptr]) { |
| return fc.formatExpr("new Uint8Array(8)") |
| } |
| switch t.Elem().Underlying().(type) { |
| case *types.Struct, *types.Array: |
| return fc.formatExpr("%e", fc.zeroValue(t.Elem())) |
| default: |
| return fc.formatExpr("$newDataPointer(%e, %s)", fc.zeroValue(t.Elem()), fc.typeName(t)) |
| } |
| case "make": |
| switch argType := fc.typeOf(args[0]).Underlying().(type) { |
| case *types.Slice: |
| t := fc.typeName(fc.typeOf(args[0])) |
| if len(args) == 3 { |
| return fc.formatExpr("$makeSlice(%s, %f, %f)", t, args[1], args[2]) |
| } |
| return fc.formatExpr("$makeSlice(%s, %f)", t, args[1]) |
| case *types.Map: |
| if len(args) == 2 && fc.pkgCtx.Types[args[1]].Value == nil { |
| return fc.formatExpr(`((%1f < 0 || %1f > 2147483647) ? $throwRuntimeError("makemap: size out of range") : new $global.Map())`, args[1]) |
| } |
| return fc.formatExpr("new $global.Map()") |
| case *types.Chan: |
| length := "0" |
| if len(args) == 2 { |
| length = fc.formatExpr("%f", args[1]).String() |
| } |
| return fc.formatExpr("new $Chan(%s, %s)", fc.typeName(fc.typeOf(args[0]).Underlying().(*types.Chan).Elem()), length) |
| default: |
| panic(fmt.Sprintf("Unhandled make type: %T\n", argType)) |
| } |
| case "len": |
| switch argType := fc.typeOf(args[0]).Underlying().(type) { |
| case *types.Basic: |
| return fc.formatExpr("%e.length", args[0]) |
| case *types.Slice: |
| return fc.formatExpr("%e.$length", args[0]) |
| case *types.Pointer: |
| return fc.formatExpr("(%e, %d)", args[0], argType.Elem().(*types.Array).Len()) |
| case *types.Map: |
| return fc.formatExpr("(%e ? %e.size : 0)", args[0], args[0]) |
| case *types.Chan: |
| return fc.formatExpr("%e.$buffer.length", args[0]) |
| // length of array is constant |
| default: |
| panic(fmt.Sprintf("Unhandled len type: %T\n", argType)) |
| } |
| case "cap": |
| switch argType := fc.typeOf(args[0]).Underlying().(type) { |
| case *types.Slice, *types.Chan: |
| return fc.formatExpr("%e.$capacity", args[0]) |
| case *types.Pointer: |
| return fc.formatExpr("(%e, %d)", args[0], argType.Elem().(*types.Array).Len()) |
| // capacity of array is constant |
| default: |
| panic(fmt.Sprintf("Unhandled cap type: %T\n", argType)) |
| } |
| case "panic": |
| return fc.formatExpr("$panic(%s)", fc.translateImplicitConversion(args[0], types.NewInterfaceType(nil, nil))) |
| case "append": |
| if ellipsis || len(args) == 1 { |
| argStr := fc.translateArgs(sig, args, ellipsis) |
| return fc.formatExpr("$appendSlice(%s, %s)", argStr[0], argStr[1]) |
| } |
| sliceType := sig.Results().At(0).Type().Underlying().(*types.Slice) |
| return fc.formatExpr("$append(%e, %s)", args[0], strings.Join(fc.translateExprSlice(args[1:], sliceType.Elem()), ", ")) |
| case "delete": |
| args = fc.expandTupleArgs(args) |
| keyType := fc.typeOf(args[0]).Underlying().(*types.Map).Key() |
| return fc.formatExpr( |
| `$mapDelete(%1e, %2s.keyFor(%3s))`, |
| args[0], |
| fc.typeName(keyType), |
| fc.translateImplicitConversion(args[1], keyType), |
| ) |
| case "copy": |
| args = fc.expandTupleArgs(args) |
| if basic, isBasic := fc.typeOf(args[1]).Underlying().(*types.Basic); isBasic && isString(basic) { |
| return fc.formatExpr("$copyString(%e, %e)", args[0], args[1]) |
| } |
| return fc.formatExpr("$copySlice(%e, %e)", args[0], args[1]) |
| case "print": |
| args = fc.expandTupleArgs(args) |
| return fc.formatExpr("$print(%s)", strings.Join(fc.translateExprSlice(args, nil), ", ")) |
| case "println": |
| args = fc.expandTupleArgs(args) |
| return fc.formatExpr("console.log(%s)", strings.Join(fc.translateExprSlice(args, nil), ", ")) |
| case "complex": |
| argStr := fc.translateArgs(sig, args, ellipsis) |
| return fc.formatExpr("new %s(%s, %s)", fc.typeName(sig.Results().At(0).Type()), argStr[0], argStr[1]) |
| case "real": |
| return fc.formatExpr("%e.$real", args[0]) |
| case "imag": |
| return fc.formatExpr("%e.$imag", args[0]) |
| case "recover": |
| return fc.formatExpr("$recover()") |
| case "close": |
| return fc.formatExpr(`$close(%e)`, args[0]) |
| case "Sizeof": |
| return fc.formatExpr("%d", sizes32.Sizeof(fc.typeOf(args[0]))) |
| case "Alignof": |
| return fc.formatExpr("%d", sizes32.Alignof(fc.typeOf(args[0]))) |
| case "Offsetof": |
| sel, _ := fc.selectionOf(astutil.RemoveParens(args[0]).(*ast.SelectorExpr)) |
| return fc.formatExpr("%d", typesutil.OffsetOf(sizes32, sel)) |
| default: |
| panic(fmt.Sprintf("Unhandled builtin: %s\n", name)) |
| } |
| } |
| |
| func (fc *funcContext) identifierConstant(expr ast.Expr) (string, bool) { |
| val := fc.pkgCtx.Types[expr].Value |
| if val == nil { |
| return "", false |
| } |
| s := constant.StringVal(val) |
| if len(s) == 0 { |
| return "", false |
| } |
| for i, c := range s { |
| if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i > 0 && c >= '0' && c <= '9') || c == '_' || c == '$') { |
| return "", false |
| } |
| } |
| return s, true |
| } |
| |
| func (fc *funcContext) translateExprSlice(exprs []ast.Expr, desiredType types.Type) []string { |
| parts := make([]string, len(exprs)) |
| for i, expr := range exprs { |
| parts[i] = fc.translateImplicitConversion(expr, desiredType).String() |
| } |
| return parts |
| } |
| |
| func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type) *expression { |
| exprType := fc.typeOf(expr) |
| if types.Identical(exprType, desiredType) { |
| return fc.translateExpr(expr) |
| } |
| |
| if fc.pkgCtx.Pkg.Path() == "reflect" || fc.pkgCtx.Pkg.Path() == "internal/reflectlite" { |
| if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(fc.typeOf(call.Fun), types.Typ[types.UnsafePointer]) { |
| if ptr, isPtr := desiredType.(*types.Pointer); isPtr { |
| if named, isNamed := ptr.Elem().(*types.Named); isNamed { |
| switch named.Obj().Name() { |
| case "arrayType", "chanType", "funcType", "interfaceType", "mapType", "ptrType", "sliceType", "structType": |
| return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion |
| default: |
| return fc.translateExpr(expr) |
| } |
| } |
| } |
| } |
| } |
| |
| switch t := desiredType.Underlying().(type) { |
| case *types.Basic: |
| switch { |
| case isInteger(t): |
| basicExprType := exprType.Underlying().(*types.Basic) |
| switch { |
| case is64Bit(t): |
| if !is64Bit(basicExprType) { |
| if basicExprType.Kind() == types.Uintptr { // this might be an Object returned from reflect.Value.Pointer() |
| return fc.formatExpr("new %1s(0, %2e.constructor === Number ? %2e : 1)", fc.typeName(desiredType), expr) |
| } |
| return fc.formatExpr("new %s(0, %e)", fc.typeName(desiredType), expr) |
| } |
| return fc.formatExpr("new %1s(%2h, %2l)", fc.typeName(desiredType), expr) |
| case is64Bit(basicExprType): |
| if !isUnsigned(t) && !isUnsigned(basicExprType) { |
| return fc.fixNumber(fc.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t) |
| } |
| return fc.fixNumber(fc.formatExpr("%s.$low", fc.translateExpr(expr)), t) |
| case types.Identical(exprType, types.Typ[types.UnsafePointer]): |
| return fc.translateExpr(expr) |
| default: |
| return fc.fixNumber(fc.translateExpr(expr), t) |
| } |
| case isFloat(t): |
| if t.Kind() == types.Float32 && exprType.Underlying().(*types.Basic).Kind() == types.Float64 { |
| return fc.formatExpr("$fround(%e)", expr) |
| } |
| return fc.formatExpr("%f", expr) |
| case isComplex(t): |
| return fc.formatExpr("new %1s(%2r, %2i)", fc.typeName(desiredType), expr) |
| case isString(t): |
| value := fc.translateExpr(expr) |
| switch et := exprType.Underlying().(type) { |
| case *types.Basic: |
| if is64Bit(et) { |
| value = fc.formatExpr("%s.$low", value) |
| } |
| if isNumeric(et) { |
| return fc.formatExpr("$encodeRune(%s)", value) |
| } |
| return value |
| case *types.Slice: |
| if types.Identical(et.Elem().Underlying(), types.Typ[types.Rune]) { |
| return fc.formatExpr("$runesToString(%s)", value) |
| } |
| return fc.formatExpr("$bytesToString(%s)", value) |
| default: |
| panic(fmt.Sprintf("Unhandled conversion: %v\n", et)) |
| } |
| case t.Kind() == types.UnsafePointer: |
| if unary, isUnary := expr.(*ast.UnaryExpr); isUnary && unary.Op == token.AND { |
| if indexExpr, isIndexExpr := unary.X.(*ast.IndexExpr); isIndexExpr { |
| return fc.formatExpr("$sliceToNativeArray(%s)", fc.translateConversionToSlice(indexExpr.X, types.NewSlice(types.Typ[types.Uint8]))) |
| } |
| if ident, isIdent := unary.X.(*ast.Ident); isIdent && ident.Name == "_zero" { |
| return fc.formatExpr("new Uint8Array(0)") |
| } |
| } |
| if ptr, isPtr := fc.typeOf(expr).(*types.Pointer); fc.pkgCtx.Pkg.Path() == "syscall" && isPtr { |
| if s, isStruct := ptr.Elem().Underlying().(*types.Struct); isStruct { |
| array := fc.newLocalVariable("_array") |
| target := fc.newLocalVariable("_struct") |
| fc.Printf("%s = new Uint8Array(%d);", array, sizes32.Sizeof(s)) |
| fc.Delayed(func() { |
| fc.Printf("%s = %s, %s;", target, fc.translateExpr(expr), fc.loadStruct(array, target, s)) |
| }) |
| return fc.formatExpr("%s", array) |
| } |
| } |
| if call, ok := expr.(*ast.CallExpr); ok { |
| if id, ok := call.Fun.(*ast.Ident); ok && id.Name == "new" { |
| return fc.formatExpr("new Uint8Array(%d)", int(sizes32.Sizeof(fc.typeOf(call.Args[0])))) |
| } |
| } |
| } |
| |
| case *types.Slice: |
| switch et := exprType.Underlying().(type) { |
| case *types.Basic: |
| if isString(et) { |
| if types.Identical(t.Elem().Underlying(), types.Typ[types.Rune]) { |
| return fc.formatExpr("new %s($stringToRunes(%e))", fc.typeName(desiredType), expr) |
| } |
| return fc.formatExpr("new %s($stringToBytes(%e))", fc.typeName(desiredType), expr) |
| } |
| case *types.Array, *types.Pointer: |
| return fc.formatExpr("new %s(%e)", fc.typeName(desiredType), expr) |
| } |
| |
| case *types.Pointer: |
| if types.Identical(exprType, types.Typ[types.UntypedNil]) { |
| // Fall through to the fc.translateImplicitConversionWithCloning(), which |
| // handles conversion from untyped nil to a pointer type. |
| break |
| } |
| |
| switch ptrElType := t.Elem().Underlying().(type) { |
| case *types.Array: // (*[N]T)(expr) — converting expr to a pointer to an array. |
| if _, ok := exprType.Underlying().(*types.Slice); ok { |
| return fc.formatExpr("$sliceToGoArray(%e, %s)", expr, fc.typeName(desiredType)) |
| } |
| // TODO(nevkontakte): Is this just for aliased types (e.g. `type a [4]byte`)? |
| return fc.translateExpr(expr) |
| case *types.Struct: // (*StructT)(expr) — converting expr to a pointer to a struct. |
| if fc.pkgCtx.Pkg.Path() == "syscall" && types.Identical(exprType, types.Typ[types.UnsafePointer]) { |
| // Special case: converting an unsafe pointer to a byte array into a |
| // struct pointer when handling syscalls. |
| // TODO(nevkontakte): Add a runtime assertion that the unsafe.Pointer is |
| // indeed pointing at a byte array. |
| array := fc.newLocalVariable("_array") |
| target := fc.newLocalVariable("_struct") |
| return fc.formatExpr("(%s = %e, %s = %e, %s, %s)", array, expr, target, fc.zeroValue(t.Elem()), fc.loadStruct(array, target, ptrElType), target) |
| } |
| // Convert between structs of different types but identical layouts, |
| // for example: |
| // type A struct { foo int }; type B A; var a *A = &A{42}; var b *B = (*B)(a) |
| // |
| // TODO(nevkontakte): Should this only apply when exprType is a pointer to a |
| // struct as well? |
| return fc.formatExpr("$pointerOfStructConversion(%e, %s)", expr, fc.typeName(desiredType)) |
| } |
| |
| if types.Identical(exprType, types.Typ[types.UnsafePointer]) { |
| // TODO(nevkontakte): Why do we fall through to the implicit conversion here? |
| // Conversion from unsafe.Pointer() requires explicit type conversion: https://play.golang.org/p/IQxtmpn1wgc. |
| // Possibly related to https://github.com/gopherjs/gopherjs/issues/1001. |
| break // Fall through to fc.translateImplicitConversionWithCloning() below. |
| } |
| // Handle remaining cases, for example: |
| // type iPtr *int; var c int = 42; println((iPtr)(&c)); |
| // TODO(nevkontakte): Are there any other cases that fall into this case? |
| exprTypeElem := exprType.Underlying().(*types.Pointer).Elem() |
| ptrVar := fc.newLocalVariable("_ptr") |
| getterConv := fc.translateConversion(fc.setType(&ast.StarExpr{X: fc.newIdent(ptrVar, exprType)}, exprTypeElem), t.Elem()) |
| setterConv := fc.translateConversion(fc.newIdent("$v", t.Elem()), exprTypeElem) |
| return fc.formatExpr("(%1s = %2e, new %3s(function() { return %4s; }, function($v) { %1s.$set(%5s); }, %1s.$target))", ptrVar, expr, fc.typeName(desiredType), getterConv, setterConv) |
| |
| case *types.Interface: |
| if types.Identical(exprType, types.Typ[types.UnsafePointer]) { |
| return fc.translateExpr(expr) |
| } |
| } |
| |
| return fc.translateImplicitConversionWithCloning(expr, desiredType) |
| } |
| |
| func (fc *funcContext) translateImplicitConversionWithCloning(expr ast.Expr, desiredType types.Type) *expression { |
| switch desiredType.Underlying().(type) { |
| case *types.Struct, *types.Array: |
| return fc.formatExpr("$clone(%e, %s)", expr, fc.typeName(desiredType)) |
| } |
| |
| return fc.translateImplicitConversion(expr, desiredType) |
| } |
| |
| func (fc *funcContext) translateImplicitConversion(expr ast.Expr, desiredType types.Type) *expression { |
| if desiredType == nil { |
| return fc.translateExpr(expr) |
| } |
| |
| exprType := fc.typeOf(expr) |
| if types.Identical(exprType, desiredType) { |
| return fc.translateExpr(expr) |
| } |
| |
| basicExprType, isBasicExpr := exprType.Underlying().(*types.Basic) |
| if isBasicExpr && basicExprType.Kind() == types.UntypedNil { |
| return fc.formatExpr("%e", fc.zeroValue(desiredType)) |
| } |
| |
| switch desiredType.Underlying().(type) { |
| case *types.Slice: |
| return fc.formatExpr("$convertSliceType(%1e, %2s)", expr, fc.typeName(desiredType)) |
| |
| case *types.Interface: |
| if typesutil.IsJsObject(exprType) { |
| // wrap JS object into js.Object struct when converting to interface |
| return fc.formatExpr("new $jsObjectPtr(%e)", expr) |
| } |
| if isWrapped(exprType) { |
| return fc.formatExpr("new %s(%e)", fc.typeName(exprType), expr) |
| } |
| if _, isStruct := exprType.Underlying().(*types.Struct); isStruct { |
| return fc.formatExpr("new %1e.constructor.elem(%1e)", expr) |
| } |
| } |
| |
| return fc.translateExpr(expr) |
| } |
| |
| func (fc *funcContext) translateConversionToSlice(expr ast.Expr, desiredType types.Type) *expression { |
| switch fc.typeOf(expr).Underlying().(type) { |
| case *types.Array, *types.Pointer: |
| return fc.formatExpr("new %s(%e)", fc.typeName(desiredType), expr) |
| } |
| return fc.translateExpr(expr) |
| } |
| |
| func (fc *funcContext) loadStruct(array, target string, s *types.Struct) string { |
| view := fc.newLocalVariable("_view") |
| code := fmt.Sprintf("%s = new DataView(%s.buffer, %s.byteOffset)", view, array, array) |
| var fields []*types.Var |
| var collectFields func(s *types.Struct, path string) |
| collectFields = func(s *types.Struct, path string) { |
| for i := 0; i < s.NumFields(); i++ { |
| field := s.Field(i) |
| if fs, isStruct := field.Type().Underlying().(*types.Struct); isStruct { |
| collectFields(fs, path+"."+fieldName(s, i)) |
| continue |
| } |
| fields = append(fields, types.NewVar(0, nil, path+"."+fieldName(s, i), field.Type())) |
| } |
| } |
| collectFields(s, target) |
| offsets := sizes32.Offsetsof(fields) |
| for i, field := range fields { |
| switch t := field.Type().Underlying().(type) { |
| case *types.Basic: |
| if isNumeric(t) { |
| if is64Bit(t) { |
| code += fmt.Sprintf(", %s = new %s(%s.getUint32(%d, true), %s.getUint32(%d, true))", field.Name(), fc.typeName(field.Type()), view, offsets[i]+4, view, offsets[i]) |
| break |
| } |
| code += fmt.Sprintf(", %s = %s.get%s(%d, true)", field.Name(), view, toJavaScriptType(t), offsets[i]) |
| } |
| case *types.Array: |
| code += fmt.Sprintf(`, %s = new ($nativeArray(%s))(%s.buffer, $min(%s.byteOffset + %d, %s.buffer.byteLength))`, field.Name(), typeKind(t.Elem()), array, array, offsets[i], array) |
| } |
| // TODO(nevkontakte): Explicitly panic if unsupported field type is encountered? |
| } |
| return code |
| } |
| |
| func (fc *funcContext) fixNumber(value *expression, basic *types.Basic) *expression { |
| switch basic.Kind() { |
| case types.Int8: |
| return fc.formatParenExpr("%s << 24 >> 24", value) |
| case types.Uint8: |
| return fc.formatParenExpr("%s << 24 >>> 24", value) |
| case types.Int16: |
| return fc.formatParenExpr("%s << 16 >> 16", value) |
| case types.Uint16: |
| return fc.formatParenExpr("%s << 16 >>> 16", value) |
| case types.Int32, types.Int, types.UntypedInt: |
| return fc.formatParenExpr("%s >> 0", value) |
| case types.Uint32, types.Uint, types.Uintptr: |
| return fc.formatParenExpr("%s >>> 0", value) |
| case types.Float32: |
| return fc.formatExpr("$fround(%s)", value) |
| case types.Float64: |
| return value |
| default: |
| panic(fmt.Sprintf("fixNumber: unhandled basic.Kind(): %s", basic.String())) |
| } |
| } |
| |
| func (fc *funcContext) internalize(s *expression, t types.Type) *expression { |
| if typesutil.IsJsObject(t) { |
| return s |
| } |
| switch u := t.Underlying().(type) { |
| case *types.Basic: |
| switch { |
| case isBoolean(u): |
| return fc.formatExpr("!!(%s)", s) |
| case isInteger(u) && !is64Bit(u): |
| return fc.fixNumber(fc.formatExpr("$parseInt(%s)", s), u) |
| case isFloat(u): |
| return fc.formatExpr("$parseFloat(%s)", s) |
| } |
| } |
| return fc.formatExpr("$internalize(%s, %s)", s, fc.typeName(t)) |
| } |
| |
| func (fc *funcContext) formatExpr(format string, a ...interface{}) *expression { |
| return fc.formatExprInternal(format, a, false) |
| } |
| |
| func (fc *funcContext) formatParenExpr(format string, a ...interface{}) *expression { |
| return fc.formatExprInternal(format, a, true) |
| } |
| |
| func (fc *funcContext) formatExprInternal(format string, a []interface{}, parens bool) *expression { |
| processFormat := func(f func(uint8, uint8, int)) { |
| n := 0 |
| for i := 0; i < len(format); i++ { |
| b := format[i] |
| if b == '%' { |
| i++ |
| k := format[i] |
| if k >= '0' && k <= '9' { |
| n = int(k - '0' - 1) |
| i++ |
| k = format[i] |
| } |
| f(0, k, n) |
| n++ |
| continue |
| } |
| f(b, 0, 0) |
| } |
| } |
| |
| counts := make([]int, len(a)) |
| processFormat(func(b, k uint8, n int) { |
| switch k { |
| case 'e', 'f', 'h', 'l', 'r', 'i': |
| counts[n]++ |
| } |
| }) |
| |
| out := bytes.NewBuffer(nil) |
| vars := make([]string, len(a)) |
| hasAssignments := false |
| for i, e := range a { |
| if counts[i] <= 1 { |
| continue |
| } |
| if _, isIdent := e.(*ast.Ident); isIdent { |
| continue |
| } |
| if val := fc.pkgCtx.Types[e.(ast.Expr)].Value; val != nil { |
| continue |
| } |
| if !hasAssignments { |
| hasAssignments = true |
| out.WriteByte('(') |
| parens = false |
| } |
| v := fc.newLocalVariable("x") |
| out.WriteString(v + " = " + fc.translateExpr(e.(ast.Expr)).String() + ", ") |
| vars[i] = v |
| } |
| |
| processFormat(func(b, k uint8, n int) { |
| writeExpr := func(suffix string) { |
| if vars[n] != "" { |
| out.WriteString(vars[n] + suffix) |
| return |
| } |
| out.WriteString(fc.translateExpr(a[n].(ast.Expr)).StringWithParens() + suffix) |
| } |
| switch k { |
| case 0: |
| out.WriteByte(b) |
| case 's': |
| if e, ok := a[n].(*expression); ok { |
| out.WriteString(e.StringWithParens()) |
| return |
| } |
| out.WriteString(a[n].(string)) |
| case 'd': |
| fmt.Fprintf(out, "%d", a[n]) |
| case 't': |
| out.WriteString(a[n].(token.Token).String()) |
| case 'e': |
| e := a[n].(ast.Expr) |
| if val := fc.pkgCtx.Types[e].Value; val != nil { |
| out.WriteString(fc.translateExpr(e).String()) |
| return |
| } |
| writeExpr("") |
| case 'f': |
| e := a[n].(ast.Expr) |
| if val := fc.pkgCtx.Types[e].Value; val != nil { |
| d, _ := constant.Int64Val(constant.ToInt(val)) |
| out.WriteString(strconv.FormatInt(d, 10)) |
| return |
| } |
| if is64Bit(fc.typeOf(e).Underlying().(*types.Basic)) { |
| out.WriteString("$flatten64(") |
| writeExpr("") |
| out.WriteString(")") |
| return |
| } |
| writeExpr("") |
| case 'h': |
| e := a[n].(ast.Expr) |
| if val := fc.pkgCtx.Types[e].Value; val != nil { |
| d, _ := constant.Uint64Val(constant.ToInt(val)) |
| if fc.typeOf(e).Underlying().(*types.Basic).Kind() == types.Int64 { |
| out.WriteString(strconv.FormatInt(int64(d)>>32, 10)) |
| return |
| } |
| out.WriteString(strconv.FormatUint(d>>32, 10)) |
| return |
| } |
| writeExpr(".$high") |
| case 'l': |
| if val := fc.pkgCtx.Types[a[n].(ast.Expr)].Value; val != nil { |
| d, _ := constant.Uint64Val(constant.ToInt(val)) |
| out.WriteString(strconv.FormatUint(d&(1<<32-1), 10)) |
| return |
| } |
| writeExpr(".$low") |
| case 'r': |
| if val := fc.pkgCtx.Types[a[n].(ast.Expr)].Value; val != nil { |
| r, _ := constant.Float64Val(constant.Real(val)) |
| out.WriteString(strconv.FormatFloat(r, 'g', -1, 64)) |
| return |
| } |
| writeExpr(".$real") |
| case 'i': |
| if val := fc.pkgCtx.Types[a[n].(ast.Expr)].Value; val != nil { |
| i, _ := constant.Float64Val(constant.Imag(val)) |
| out.WriteString(strconv.FormatFloat(i, 'g', -1, 64)) |
| return |
| } |
| writeExpr(".$imag") |
| case '%': |
| out.WriteRune('%') |
| default: |
| panic(fmt.Sprintf("formatExpr: %%%c%d", k, n)) |
| } |
| }) |
| |
| if hasAssignments { |
| out.WriteByte(')') |
| } |
| return &expression{str: out.String(), parens: parens} |
| } |