@@ -5,6 +5,7 @@ package compiler
5
5
6
6
import (
7
7
"bytes"
8
+ "errors"
8
9
"fmt"
9
10
"go/ast"
10
11
"go/types"
@@ -17,18 +18,21 @@ import (
17
18
"github.com/gopherjs/gopherjs/compiler/typesutil"
18
19
)
19
20
20
- // newFunctionContext creates a new nested context for a function corresponding
21
+ // nestedFunctionContext creates a new nested context for a function corresponding
21
22
// to the provided info and instance.
22
- func (fc * funcContext ) nestedFunctionContext (info * analysis.FuncInfo , sig * types. Signature , inst typeparams.Instance ) * funcContext {
23
+ func (fc * funcContext ) nestedFunctionContext (info * analysis.FuncInfo , inst typeparams.Instance ) * funcContext {
23
24
if info == nil {
24
- panic (fmt . Errorf ("missing *analysis.FuncInfo" ))
25
+ panic (errors . New ("missing *analysis.FuncInfo" ))
25
26
}
26
- if sig == nil {
27
- panic (fmt . Errorf ("missing *types.Signature " ))
27
+ if inst . Object == nil {
28
+ panic (errors . New ("missing inst.Object " ))
28
29
}
30
+ o := inst .Object .(* types.Func )
31
+ sig := o .Type ().(* types.Signature )
29
32
30
33
c := & funcContext {
31
34
FuncInfo : info ,
35
+ instance : inst ,
32
36
pkgCtx : fc .pkgCtx ,
33
37
parent : fc ,
34
38
allVars : make (map [string ]int , len (fc .allVars )),
@@ -53,53 +57,105 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types
53
57
c .objectNames = map [types.Object ]string {}
54
58
}
55
59
60
+ // Synthesize an identifier by which the function may reference itself. Since
61
+ // it appears in the stack trace, it's useful to include the receiver type in
62
+ // it.
63
+ funcRef := o .Name ()
64
+ if recvType := typesutil .RecvType (sig ); recvType != nil {
65
+ funcRef = recvType .Obj ().Name () + midDot + funcRef
66
+ }
67
+ c .funcRef = c .newVariable (funcRef , true /*pkgLevel*/ )
68
+
69
+ return c
70
+ }
71
+
72
+ // namedFuncContext creates a new funcContext for a named Go function
73
+ // (standalone or method).
74
+ func (fc * funcContext ) namedFuncContext (inst typeparams.Instance ) * funcContext {
75
+ info := fc .pkgCtx .FuncDeclInfos [inst .Object .(* types.Func )]
76
+ c := fc .nestedFunctionContext (info , inst )
77
+
78
+ return c
79
+ }
80
+
81
+ // literalFuncContext creates a new funcContext for a function literal. Since
82
+ // go/types doesn't generate *types.Func objects for function literals, we
83
+ // generate a synthetic one for it.
84
+ func (fc * funcContext ) literalFuncContext (fun * ast.FuncLit ) * funcContext {
85
+ info := fc .pkgCtx .FuncLitInfos [fun ]
86
+ sig := fc .pkgCtx .TypeOf (fun ).(* types.Signature )
87
+ o := types .NewFunc (fun .Pos (), fc .pkgCtx .Pkg , fc .newLitFuncName (), sig )
88
+ inst := typeparams.Instance {Object : o }
89
+
90
+ c := fc .nestedFunctionContext (info , inst )
56
91
return c
57
92
}
58
93
59
94
// translateTopLevelFunction translates a top-level function declaration
60
- // (standalone function or method) into a corresponding JS function.
95
+ // (standalone function or method) into a corresponding JS function. Must be
96
+ // called on the function context created for the function corresponding instance.
61
97
//
62
- // Returns a string with a JavaScript statements that define the function or
98
+ // Returns a string with JavaScript statements that define the function or
63
99
// method. For methods it returns declarations for both value- and
64
100
// pointer-receiver (if appropriate).
65
- func (fc * funcContext ) translateTopLevelFunction (fun * ast.FuncDecl , inst typeparams. Instance ) []byte {
101
+ func (fc * funcContext ) translateTopLevelFunction (fun * ast.FuncDecl ) []byte {
66
102
if fun .Recv == nil {
67
- return fc .translateStandaloneFunction (fun , inst )
103
+ return fc .translateStandaloneFunction (fun )
68
104
}
69
105
70
- o := inst .Object .(* types.Func )
71
- info := fc .pkgCtx .FuncDeclInfos [o ]
106
+ return fc .translateMethod (fun )
107
+ }
108
+
109
+ // translateStandaloneFunction translates a package-level function.
110
+ //
111
+ // It returns JS statements which define the corresponding function in a
112
+ // package context. Exported functions are also assigned to the `$pkg` object.
113
+ func (fc * funcContext ) translateStandaloneFunction (fun * ast.FuncDecl ) []byte {
114
+ o := fc .instance .Object .(* types.Func )
115
+
116
+ if fun .Recv != nil {
117
+ panic (fmt .Errorf ("expected standalone function, got method: %s" , o ))
118
+ }
119
+
120
+ lvalue := fc .instName (fc .instance )
121
+
122
+ if fun .Body == nil {
123
+ return []byte (fmt .Sprintf ("\t %s = %s;\n " , lvalue , fc .unimplementedFunction (o )))
124
+ }
125
+
126
+ body := fc .translateFunctionBody (fun .Type , nil , fun .Body )
127
+ code := bytes .NewBuffer (nil )
128
+ fmt .Fprintf (code , "\t %s = %s;\n " , lvalue , body )
129
+ if fun .Name .IsExported () {
130
+ fmt .Fprintf (code , "\t $pkg.%s = %s;\n " , encodeIdent (fun .Name .Name ), lvalue )
131
+ }
132
+ return code .Bytes ()
133
+ }
134
+
135
+ // translateMethod translates a named type method.
136
+ //
137
+ // It returns one or more JS statements which define the method. Methods with
138
+ // non-pointer receiver are automatically defined for the pointer-receiver type.
139
+ func (fc * funcContext ) translateMethod (fun * ast.FuncDecl ) []byte {
140
+ o := fc .instance .Object .(* types.Func )
141
+ funName := fc .methodName (o )
72
142
73
- sig := o .Type ().(* types.Signature )
74
143
// primaryFunction generates a JS function equivalent of the current Go function
75
144
// and assigns it to the JS expression defined by lvalue.
76
145
primaryFunction := func (lvalue string ) []byte {
77
146
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 ( )))
147
+ return []byte (fmt .Sprintf ("\t %s = %s ;\n " , lvalue , fc . unimplementedFunction ( o )))
79
148
}
80
149
81
150
var recv * ast.Ident
82
151
if fun .Recv != nil && fun .Recv .List [0 ].Names != nil {
83
152
recv = fun .Recv .List [0 ].Names [0 ]
84
153
}
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 )
154
+ fun := fc .translateFunctionBody (fun .Type , recv , fun .Body )
99
155
return []byte (fmt .Sprintf ("\t %s = %s;\n " , lvalue , fun ))
100
156
}
101
157
102
- recvInst := inst .Recv ()
158
+ recvInst := fc . instance .Recv ()
103
159
recvInstName := fc .instName (recvInst )
104
160
recvType := recvInst .Object .Type ().(* types.Named )
105
161
@@ -108,78 +164,59 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl, inst typepar
108
164
prototypeVar := fmt .Sprintf ("%s.prototype.%s" , recvInstName , funName )
109
165
ptrPrototypeVar := fmt .Sprintf ("$ptrType(%s).prototype.%s" , recvInstName , funName )
110
166
111
- code := bytes .NewBuffer (nil )
167
+ // Methods with pointer-receiver are only attached to the pointer-receiver type.
168
+ if _ , isPointer := fc .sig .Sig .Recv ().Type ().(* types.Pointer ); isPointer {
169
+ return primaryFunction (ptrPrototypeVar )
170
+ }
171
+
172
+ // Methods with non-pointer receivers must be defined both for the pointer
173
+ // and non-pointer types. To minimize generated code size, we generate a
174
+ // complete implementation for only one receiver (non-pointer for most types)
175
+ // and define a proxy function on the other, which converts the receiver type
176
+ // and forwards the call to the primary implementation.
177
+ proxyFunction := func (lvalue , receiver string ) []byte {
178
+ fun := fmt .Sprintf ("function(...$args) { return %s.%s(...$args); }" , receiver , funName )
179
+ return []byte (fmt .Sprintf ("\t %s = %s;\n " , lvalue , fun ))
180
+ }
112
181
182
+ // Structs are a special case: they are represented by JS objects and their
183
+ // methods are the underlying object's methods. Due to reference semantics of
184
+ // the JS variables, the actual backing object is considered to represent the
185
+ // pointer-to-struct type, and methods are attacher to it first and foremost.
113
186
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.
187
+ code := bytes.Buffer {}
119
188
code .Write (primaryFunction (ptrPrototypeVar ))
120
189
code .Write (proxyFunction (prototypeVar , "this.$val" ))
121
190
return code .Bytes ()
122
191
}
123
192
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
193
// Methods defined for non-pointer receiver are attached to both pointer- and
139
194
// non-pointer-receiver types.
140
- recvExpr := "this.$get()"
195
+ proxyRecvExpr := "this.$get()"
141
196
if isWrapped (recvType ) {
142
- recvExpr = fmt .Sprintf ("new %s(%s)" , recvInstName , recvExpr )
197
+ proxyRecvExpr = fmt .Sprintf ("new %s(%s)" , recvInstName , proxyRecvExpr )
143
198
}
199
+ code := bytes.Buffer {}
144
200
code .Write (primaryFunction (prototypeVar ))
145
- code .Write (proxyFunction (ptrPrototypeVar , recvExpr ))
201
+ code .Write (proxyFunction (ptrPrototypeVar , proxyRecvExpr ))
146
202
return code .Bytes ()
147
203
}
148
204
149
- // translateStandaloneFunction translates a package-level function.
205
+ // unimplementedFunction returns a JS function expression for a Go function
206
+ // without a body, which would throw an exception if called.
150
207
//
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 ()
208
+ // In Go such functions are either used with a //go:linkname directive or with
209
+ // assembler intrinsics, only former of which is supported by GopherJS.
210
+ func (fc * funcContext ) unimplementedFunction (o * types.Func ) string {
211
+ return fmt .Sprintf ("function() {\n \t \t $throwRuntimeError(\" native function not implemented: %s\" );\n \t }" , o .FullName ())
175
212
}
176
213
177
214
// translateFunctionBody translates body of a top-level or literal function.
178
215
//
179
216
// It returns a JS function expression that represents the given Go function.
180
217
// Function receiver must have been created with nestedFunctionContext() to have
181
218
// required metadata set up.
182
- func (fc * funcContext ) translateFunctionBody (typ * ast.FuncType , recv * ast.Ident , body * ast.BlockStmt , funcRef string ) string {
219
+ func (fc * funcContext ) translateFunctionBody (typ * ast.FuncType , recv * ast.Ident , body * ast.BlockStmt ) string {
183
220
prevEV := fc .pkgCtx .escapingVars
184
221
185
222
// Generate a list of function argument variables. Since Go allows nameless
@@ -233,7 +270,7 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
233
270
234
271
sort .Strings (fc .localVars )
235
272
236
- var prefix , suffix , functionName string
273
+ var prefix , suffix string
237
274
238
275
if len (fc .Flattened ) != 0 {
239
276
// $s contains an index of the switch case a blocking function reached
@@ -254,21 +291,19 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
254
291
localVarDefs := "" // Function-local var declaration at the top.
255
292
256
293
if len (fc .Blocking ) != 0 {
257
- if funcRef == "" {
258
- funcRef = "$b"
259
- functionName = " $b"
260
- }
261
-
262
294
localVars := append ([]string {}, fc .localVars ... )
263
295
// There are several special variables involved in handling blocking functions:
264
296
// $r is sometimes used as a temporary variable to store blocking call result.
265
297
// $c indicates that a function is being resumed after a blocking call when set to true.
266
298
// $f is an object used to save and restore function context for blocking calls.
267
299
localVars = append (localVars , "$r" )
300
+ // funcRef identifies the function object itself, so it doesn't need to be saved
301
+ // or restored.
302
+ localVars = removeMatching (localVars , fc .funcRef )
268
303
// If a blocking function is being resumed, initialize local variables from the saved context.
269
304
localVarDefs = fmt .Sprintf ("var {%s, $c} = $restore(this, {%s});\n " , strings .Join (localVars , ", " ), strings .Join (args , ", " ))
270
305
// 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 , ", " ))
306
+ saveContext := fmt .Sprintf ("var $f = {$blk: " + fc . funcRef + ", $c: true, $r, %s};" , strings .Join (fc .localVars , ", " ))
272
307
273
308
suffix = " " + saveContext + "return $f;" + suffix
274
309
} else if len (fc .localVars ) > 0 {
@@ -316,5 +351,5 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
316
351
317
352
fc .pkgCtx .escapingVars = prevEV
318
353
319
- return fmt .Sprintf ("function%s(%s) {\n %s%s}" , functionName , strings .Join (args , ", " ), bodyOutput , fc .Indentation (0 ))
354
+ return fmt .Sprintf ("function %s(%s) {\n %s%s}" , fc . funcRef , strings .Join (args , ", " ), bodyOutput , fc .Indentation (0 ))
320
355
}
0 commit comments