|
| 1 | +package compiler |
| 2 | + |
| 3 | +// functions.go contains logic responsible for translating top-level functions |
| 4 | +// and function literals. |
| 5 | + |
| 6 | +import ( |
| 7 | + "bytes" |
| 8 | + "fmt" |
| 9 | + "go/ast" |
| 10 | + "go/types" |
| 11 | + "sort" |
| 12 | + "strings" |
| 13 | + |
| 14 | + "github.com/gopherjs/gopherjs/compiler/analysis" |
| 15 | + "github.com/gopherjs/gopherjs/compiler/astutil" |
| 16 | + "github.com/gopherjs/gopherjs/compiler/internal/typeparams" |
| 17 | + "github.com/gopherjs/gopherjs/compiler/typesutil" |
| 18 | +) |
| 19 | + |
| 20 | +// newFunctionContext creates a new nested context for a function corresponding |
| 21 | +// to the provided info and instance. |
| 22 | +func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature, inst typeparams.Instance) *funcContext { |
| 23 | + if info == nil { |
| 24 | + panic(fmt.Errorf("missing *analysis.FuncInfo")) |
| 25 | + } |
| 26 | + if sig == nil { |
| 27 | + panic(fmt.Errorf("missing *types.Signature")) |
| 28 | + } |
| 29 | + |
| 30 | + c := &funcContext{ |
| 31 | + FuncInfo: info, |
| 32 | + pkgCtx: fc.pkgCtx, |
| 33 | + parent: fc, |
| 34 | + allVars: make(map[string]int, len(fc.allVars)), |
| 35 | + localVars: []string{}, |
| 36 | + flowDatas: map[*types.Label]*flowData{nil: {}}, |
| 37 | + caseCounter: 1, |
| 38 | + labelCases: make(map[*types.Label]int), |
| 39 | + typeResolver: fc.typeResolver, |
| 40 | + objectNames: map[types.Object]string{}, |
| 41 | + sig: &typesutil.Signature{Sig: sig}, |
| 42 | + } |
| 43 | + for k, v := range fc.allVars { |
| 44 | + c.allVars[k] = v |
| 45 | + } |
| 46 | + |
| 47 | + if sig.TypeParams().Len() > 0 { |
| 48 | + c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, typeparams.ToSlice(sig.TypeParams()), inst.TArgs) |
| 49 | + } else if sig.RecvTypeParams().Len() > 0 { |
| 50 | + c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, typeparams.ToSlice(sig.RecvTypeParams()), inst.TArgs) |
| 51 | + } |
| 52 | + if c.objectNames == nil { |
| 53 | + c.objectNames = map[types.Object]string{} |
| 54 | + } |
| 55 | + |
| 56 | + return c |
| 57 | +} |
| 58 | + |
| 59 | +// translateTopLevelFunction translates a top-level function declaration |
| 60 | +// (standalone function or method) into a corresponding JS function. |
| 61 | +// |
| 62 | +// Returns a string with a JavaScript statements that define the function or |
| 63 | +// method. For methods it returns declarations for both value- and |
| 64 | +// pointer-receiver (if appropriate). |
| 65 | +func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte { |
| 66 | + if fun.Recv == nil { |
| 67 | + return fc.translateStandaloneFunction(fun, inst) |
| 68 | + } |
| 69 | + |
| 70 | + o := inst.Object.(*types.Func) |
| 71 | + info := fc.pkgCtx.FuncDeclInfos[o] |
| 72 | + |
| 73 | + sig := o.Type().(*types.Signature) |
| 74 | + // primaryFunction generates a JS function equivalent of the current Go function |
| 75 | + // and assigns it to the JS expression defined by lvalue. |
| 76 | + primaryFunction := func(lvalue string) []byte { |
| 77 | + if fun.Body == nil { |
| 78 | + return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName())) |
| 79 | + } |
| 80 | + |
| 81 | + var recv *ast.Ident |
| 82 | + if fun.Recv != nil && fun.Recv.List[0].Names != nil { |
| 83 | + recv = fun.Recv.List[0].Names[0] |
| 84 | + } |
| 85 | + fun := fc.nestedFunctionContext(info, sig, inst).translateFunctionBody(fun.Type, recv, fun.Body, lvalue) |
| 86 | + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) |
| 87 | + } |
| 88 | + |
| 89 | + funName := fun.Name.Name |
| 90 | + if reservedKeywords[funName] { |
| 91 | + funName += "$" |
| 92 | + } |
| 93 | + |
| 94 | + // proxyFunction generates a JS function that forwards the call to the actual |
| 95 | + // method implementation for the alternate receiver (e.g. pointer vs |
| 96 | + // non-pointer). |
| 97 | + proxyFunction := func(lvalue, receiver string) []byte { |
| 98 | + fun := fmt.Sprintf("function(...$args) { return %s.%s(...$args); }", receiver, funName) |
| 99 | + return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun)) |
| 100 | + } |
| 101 | + |
| 102 | + recvInst := inst.Recv() |
| 103 | + recvInstName := fc.instName(recvInst) |
| 104 | + recvType := recvInst.Object.Type().(*types.Named) |
| 105 | + |
| 106 | + // Objects the method should be assigned to for the plain and pointer type |
| 107 | + // of the receiver. |
| 108 | + prototypeVar := fmt.Sprintf("%s.prototype.%s", recvInstName, funName) |
| 109 | + ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", recvInstName, funName) |
| 110 | + |
| 111 | + code := bytes.NewBuffer(nil) |
| 112 | + |
| 113 | + if _, isStruct := recvType.Underlying().(*types.Struct); isStruct { |
| 114 | + // Structs are a special case: they are represented by JS objects and their |
| 115 | + // methods are the underlying object's methods. Due to reference semantics |
| 116 | + // of the JS variables, the actual backing object is considered to represent |
| 117 | + // the pointer-to-struct type, and methods are attacher to it first and |
| 118 | + // foremost. |
| 119 | + code.Write(primaryFunction(ptrPrototypeVar)) |
| 120 | + code.Write(proxyFunction(prototypeVar, "this.$val")) |
| 121 | + return code.Bytes() |
| 122 | + } |
| 123 | + |
| 124 | + if ptr, isPointer := sig.Recv().Type().(*types.Pointer); isPointer { |
| 125 | + if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray { |
| 126 | + // Pointer-to-array is another special case. |
| 127 | + // TODO(nevkontakte) Find out and document why. |
| 128 | + code.Write(primaryFunction(prototypeVar)) |
| 129 | + code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", recvInstName))) |
| 130 | + return code.Bytes() |
| 131 | + } |
| 132 | + |
| 133 | + // Methods with pointer-receiver are only attached to the pointer-receiver |
| 134 | + // type. |
| 135 | + return primaryFunction(ptrPrototypeVar) |
| 136 | + } |
| 137 | + |
| 138 | + // Methods defined for non-pointer receiver are attached to both pointer- and |
| 139 | + // non-pointer-receiver types. |
| 140 | + recvExpr := "this.$get()" |
| 141 | + if isWrapped(recvType) { |
| 142 | + recvExpr = fmt.Sprintf("new %s(%s)", recvInstName, recvExpr) |
| 143 | + } |
| 144 | + code.Write(primaryFunction(prototypeVar)) |
| 145 | + code.Write(proxyFunction(ptrPrototypeVar, recvExpr)) |
| 146 | + return code.Bytes() |
| 147 | +} |
| 148 | + |
| 149 | +// translateStandaloneFunction translates a package-level function. |
| 150 | +// |
| 151 | +// It returns a JS statements which define the corresponding function in a |
| 152 | +// package context. Exported functions are also assigned to the `$pkg` object. |
| 153 | +func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte { |
| 154 | + o := inst.Object.(*types.Func) |
| 155 | + info := fc.pkgCtx.FuncDeclInfos[o] |
| 156 | + sig := o.Type().(*types.Signature) |
| 157 | + |
| 158 | + if fun.Recv != nil { |
| 159 | + panic(fmt.Errorf("expected standalone function, got method: %s", o)) |
| 160 | + } |
| 161 | + |
| 162 | + lvalue := fc.instName(inst) |
| 163 | + |
| 164 | + if fun.Body == nil { |
| 165 | + return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName())) |
| 166 | + } |
| 167 | + |
| 168 | + body := fc.nestedFunctionContext(info, sig, inst).translateFunctionBody(fun.Type, nil, fun.Body, lvalue) |
| 169 | + code := bytes.NewBuffer(nil) |
| 170 | + fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body) |
| 171 | + if fun.Name.IsExported() { |
| 172 | + fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), lvalue) |
| 173 | + } |
| 174 | + return code.Bytes() |
| 175 | +} |
| 176 | + |
| 177 | +// translateFunctionBody translates body of a top-level or literal function. |
| 178 | +// |
| 179 | +// It returns a JS function expression that represents the given Go function. |
| 180 | +// Function receiver must have been created with nestedFunctionContext() to have |
| 181 | +// required metadata set up. |
| 182 | +func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string { |
| 183 | + prevEV := fc.pkgCtx.escapingVars |
| 184 | + |
| 185 | + // Generate a list of function argument variables. Since Go allows nameless |
| 186 | + // arguments, we have to generate synthetic names for their JS counterparts. |
| 187 | + var args []string |
| 188 | + for _, param := range typ.Params.List { |
| 189 | + if len(param.Names) == 0 { |
| 190 | + args = append(args, fc.newLocalVariable("param")) |
| 191 | + continue |
| 192 | + } |
| 193 | + for _, ident := range param.Names { |
| 194 | + if isBlank(ident) { |
| 195 | + args = append(args, fc.newLocalVariable("param")) |
| 196 | + continue |
| 197 | + } |
| 198 | + args = append(args, fc.objectName(fc.pkgCtx.Defs[ident])) |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + bodyOutput := string(fc.CatchOutput(1, func() { |
| 203 | + if len(fc.Blocking) != 0 { |
| 204 | + fc.pkgCtx.Scopes[body] = fc.pkgCtx.Scopes[typ] |
| 205 | + fc.handleEscapingVars(body) |
| 206 | + } |
| 207 | + |
| 208 | + if fc.sig != nil && fc.sig.HasNamedResults() { |
| 209 | + fc.resultNames = make([]ast.Expr, fc.sig.Sig.Results().Len()) |
| 210 | + for i := 0; i < fc.sig.Sig.Results().Len(); i++ { |
| 211 | + result := fc.sig.Sig.Results().At(i) |
| 212 | + typ := fc.typeResolver.Substitute(result.Type()) |
| 213 | + fc.Printf("%s = %s;", fc.objectName(result), fc.translateExpr(fc.zeroValue(typ)).String()) |
| 214 | + id := ast.NewIdent("") |
| 215 | + fc.pkgCtx.Uses[id] = result |
| 216 | + fc.resultNames[i] = fc.setType(id, typ) |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + if recv != nil && !isBlank(recv) { |
| 221 | + this := "this" |
| 222 | + if isWrapped(fc.typeOf(recv)) { |
| 223 | + this = "this.$val" // Unwrap receiver value. |
| 224 | + } |
| 225 | + fc.Printf("%s = %s;", fc.translateExpr(recv), this) |
| 226 | + } |
| 227 | + |
| 228 | + fc.translateStmtList(body.List) |
| 229 | + if len(fc.Flattened) != 0 && !astutil.EndsWithReturn(body.List) { |
| 230 | + fc.translateStmt(&ast.ReturnStmt{}, nil) |
| 231 | + } |
| 232 | + })) |
| 233 | + |
| 234 | + sort.Strings(fc.localVars) |
| 235 | + |
| 236 | + var prefix, suffix, functionName string |
| 237 | + |
| 238 | + if len(fc.Flattened) != 0 { |
| 239 | + // $s contains an index of the switch case a blocking function reached |
| 240 | + // before getting blocked. When execution resumes, it will allow to continue |
| 241 | + // from where we left off. |
| 242 | + fc.localVars = append(fc.localVars, "$s") |
| 243 | + prefix = prefix + " $s = $s || 0;" |
| 244 | + } |
| 245 | + |
| 246 | + if fc.HasDefer { |
| 247 | + fc.localVars = append(fc.localVars, "$deferred") |
| 248 | + suffix = " }" + suffix |
| 249 | + if len(fc.Blocking) != 0 { |
| 250 | + suffix = " }" + suffix |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + localVarDefs := "" // Function-local var declaration at the top. |
| 255 | + |
| 256 | + if len(fc.Blocking) != 0 { |
| 257 | + if funcRef == "" { |
| 258 | + funcRef = "$b" |
| 259 | + functionName = " $b" |
| 260 | + } |
| 261 | + |
| 262 | + localVars := append([]string{}, fc.localVars...) |
| 263 | + // There are several special variables involved in handling blocking functions: |
| 264 | + // $r is sometimes used as a temporary variable to store blocking call result. |
| 265 | + // $c indicates that a function is being resumed after a blocking call when set to true. |
| 266 | + // $f is an object used to save and restore function context for blocking calls. |
| 267 | + localVars = append(localVars, "$r") |
| 268 | + // If a blocking function is being resumed, initialize local variables from the saved context. |
| 269 | + localVarDefs = fmt.Sprintf("var {%s, $c} = $restore(this, {%s});\n", strings.Join(localVars, ", "), strings.Join(args, ", ")) |
| 270 | + // If the function gets blocked, save local variables for future. |
| 271 | + saveContext := fmt.Sprintf("var $f = {$blk: "+funcRef+", $c: true, $r, %s};", strings.Join(fc.localVars, ", ")) |
| 272 | + |
| 273 | + suffix = " " + saveContext + "return $f;" + suffix |
| 274 | + } else if len(fc.localVars) > 0 { |
| 275 | + // Non-blocking functions simply declare local variables with no need for restore support. |
| 276 | + localVarDefs = fmt.Sprintf("var %s;\n", strings.Join(fc.localVars, ", ")) |
| 277 | + } |
| 278 | + |
| 279 | + if fc.HasDefer { |
| 280 | + prefix = prefix + " var $err = null; try {" |
| 281 | + deferSuffix := " } catch(err) { $err = err;" |
| 282 | + if len(fc.Blocking) != 0 { |
| 283 | + deferSuffix += " $s = -1;" |
| 284 | + } |
| 285 | + if fc.resultNames == nil && fc.sig.HasResults() { |
| 286 | + deferSuffix += fmt.Sprintf(" return%s;", fc.translateResults(nil)) |
| 287 | + } |
| 288 | + deferSuffix += " } finally { $callDeferred($deferred, $err);" |
| 289 | + if fc.resultNames != nil { |
| 290 | + deferSuffix += fmt.Sprintf(" if (!$curGoroutine.asleep) { return %s; }", fc.translateResults(fc.resultNames)) |
| 291 | + } |
| 292 | + if len(fc.Blocking) != 0 { |
| 293 | + deferSuffix += " if($curGoroutine.asleep) {" |
| 294 | + } |
| 295 | + suffix = deferSuffix + suffix |
| 296 | + } |
| 297 | + |
| 298 | + if len(fc.Flattened) != 0 { |
| 299 | + prefix = prefix + " s: while (true) { switch ($s) { case 0:" |
| 300 | + suffix = " } return; }" + suffix |
| 301 | + } |
| 302 | + |
| 303 | + if fc.HasDefer { |
| 304 | + prefix = prefix + " $deferred = []; $curGoroutine.deferStack.push($deferred);" |
| 305 | + } |
| 306 | + |
| 307 | + if prefix != "" { |
| 308 | + bodyOutput = fc.Indentation(1) + "/* */" + prefix + "\n" + bodyOutput |
| 309 | + } |
| 310 | + if suffix != "" { |
| 311 | + bodyOutput = bodyOutput + fc.Indentation(1) + "/* */" + suffix + "\n" |
| 312 | + } |
| 313 | + if localVarDefs != "" { |
| 314 | + bodyOutput = fc.Indentation(1) + localVarDefs + bodyOutput |
| 315 | + } |
| 316 | + |
| 317 | + fc.pkgCtx.escapingVars = prevEV |
| 318 | + |
| 319 | + return fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(args, ", "), bodyOutput, fc.Indentation(0)) |
| 320 | +} |
0 commit comments