Skip to content

Commit 1ebb325

Browse files
authored
Merge pull request #1335 from nevkontakte/gng7
Final round of refactoring ported from the original generics branch.
2 parents 0390b03 + 4ebc56c commit 1ebb325

File tree

7 files changed

+224
-102
lines changed

7 files changed

+224
-102
lines changed

compiler/decls.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ func (fc *funcContext) newFuncDecl(fun *ast.FuncDecl, inst typeparams.Instance)
345345
}
346346

347347
d.DceDeps = fc.CollectDCEDeps(func() {
348-
d.DeclCode = fc.translateTopLevelFunction(fun, inst)
348+
d.DeclCode = fc.namedFuncContext(inst).translateTopLevelFunction(fun)
349349
})
350350
return d
351351
}

compiler/expressions.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
201201
}
202202

203203
case *ast.FuncLit:
204-
fun := fc.nestedFunctionContext(fc.pkgCtx.FuncLitInfos[e], exprType.(*types.Signature), typeparams.Instance{}).translateFunctionBody(e.Type, nil, e.Body, "")
204+
fun := fc.literalFuncContext(e).translateFunctionBody(e.Type, nil, e.Body)
205205
if len(fc.pkgCtx.escapingVars) != 0 {
206206
names := make([]string, 0, len(fc.pkgCtx.escapingVars))
207207
for obj := range fc.pkgCtx.escapingVars {
@@ -730,10 +730,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
730730
}
731731
}
732732

733-
methodName := sel.Obj().Name()
734-
if reservedKeywords[methodName] {
735-
methodName += "$"
736-
}
733+
methodName := fc.methodName(sel.Obj().(*types.Func))
737734
return fc.translateCall(e, sig, fc.formatExpr("%s.%s", recv, methodName))
738735

739736
case types.FieldVal:

compiler/functions.go

Lines changed: 120 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package compiler
55

66
import (
77
"bytes"
8+
"errors"
89
"fmt"
910
"go/ast"
1011
"go/types"
@@ -17,18 +18,21 @@ import (
1718
"github.com/gopherjs/gopherjs/compiler/typesutil"
1819
)
1920

20-
// newFunctionContext creates a new nested context for a function corresponding
21+
// nestedFunctionContext creates a new nested context for a function corresponding
2122
// 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 {
2324
if info == nil {
24-
panic(fmt.Errorf("missing *analysis.FuncInfo"))
25+
panic(errors.New("missing *analysis.FuncInfo"))
2526
}
26-
if sig == nil {
27-
panic(fmt.Errorf("missing *types.Signature"))
27+
if inst.Object == nil {
28+
panic(errors.New("missing inst.Object"))
2829
}
30+
o := inst.Object.(*types.Func)
31+
sig := o.Type().(*types.Signature)
2932

3033
c := &funcContext{
3134
FuncInfo: info,
35+
instance: inst,
3236
pkgCtx: fc.pkgCtx,
3337
parent: fc,
3438
allVars: make(map[string]int, len(fc.allVars)),
@@ -53,53 +57,105 @@ func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types
5357
c.objectNames = map[types.Object]string{}
5458
}
5559

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)
5691
return c
5792
}
5893

5994
// 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.
6197
//
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
6399
// method. For methods it returns declarations for both value- and
64100
// 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 {
66102
if fun.Recv == nil {
67-
return fc.translateStandaloneFunction(fun, inst)
103+
return fc.translateStandaloneFunction(fun)
68104
}
69105

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)
72142

73-
sig := o.Type().(*types.Signature)
74143
// primaryFunction generates a JS function equivalent of the current Go function
75144
// and assigns it to the JS expression defined by lvalue.
76145
primaryFunction := func(lvalue string) []byte {
77146
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)))
79148
}
80149

81150
var recv *ast.Ident
82151
if fun.Recv != nil && fun.Recv.List[0].Names != nil {
83152
recv = fun.Recv.List[0].Names[0]
84153
}
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)
99155
return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
100156
}
101157

102-
recvInst := inst.Recv()
158+
recvInst := fc.instance.Recv()
103159
recvInstName := fc.instName(recvInst)
104160
recvType := recvInst.Object.Type().(*types.Named)
105161

@@ -108,78 +164,59 @@ func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl, inst typepar
108164
prototypeVar := fmt.Sprintf("%s.prototype.%s", recvInstName, funName)
109165
ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", recvInstName, funName)
110166

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+
}
112181

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.
113186
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{}
119188
code.Write(primaryFunction(ptrPrototypeVar))
120189
code.Write(proxyFunction(prototypeVar, "this.$val"))
121190
return code.Bytes()
122191
}
123192

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-
138193
// Methods defined for non-pointer receiver are attached to both pointer- and
139194
// non-pointer-receiver types.
140-
recvExpr := "this.$get()"
195+
proxyRecvExpr := "this.$get()"
141196
if isWrapped(recvType) {
142-
recvExpr = fmt.Sprintf("new %s(%s)", recvInstName, recvExpr)
197+
proxyRecvExpr = fmt.Sprintf("new %s(%s)", recvInstName, proxyRecvExpr)
143198
}
199+
code := bytes.Buffer{}
144200
code.Write(primaryFunction(prototypeVar))
145-
code.Write(proxyFunction(ptrPrototypeVar, recvExpr))
201+
code.Write(proxyFunction(ptrPrototypeVar, proxyRecvExpr))
146202
return code.Bytes()
147203
}
148204

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.
150207
//
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())
175212
}
176213

177214
// translateFunctionBody translates body of a top-level or literal function.
178215
//
179216
// It returns a JS function expression that represents the given Go function.
180217
// Function receiver must have been created with nestedFunctionContext() to have
181218
// 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 {
183220
prevEV := fc.pkgCtx.escapingVars
184221

185222
// 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,
233270

234271
sort.Strings(fc.localVars)
235272

236-
var prefix, suffix, functionName string
273+
var prefix, suffix string
237274

238275
if len(fc.Flattened) != 0 {
239276
// $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,
254291
localVarDefs := "" // Function-local var declaration at the top.
255292

256293
if len(fc.Blocking) != 0 {
257-
if funcRef == "" {
258-
funcRef = "$b"
259-
functionName = " $b"
260-
}
261-
262294
localVars := append([]string{}, fc.localVars...)
263295
// There are several special variables involved in handling blocking functions:
264296
// $r is sometimes used as a temporary variable to store blocking call result.
265297
// $c indicates that a function is being resumed after a blocking call when set to true.
266298
// $f is an object used to save and restore function context for blocking calls.
267299
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)
268303
// If a blocking function is being resumed, initialize local variables from the saved context.
269304
localVarDefs = fmt.Sprintf("var {%s, $c} = $restore(this, {%s});\n", strings.Join(localVars, ", "), strings.Join(args, ", "))
270305
// 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, ", "))
272307

273308
suffix = " " + saveContext + "return $f;" + suffix
274309
} else if len(fc.localVars) > 0 {
@@ -316,5 +351,5 @@ func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident,
316351

317352
fc.pkgCtx.escapingVars = prevEV
318353

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))
320355
}

compiler/natives/src/reflect/reflect.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,26 +1778,28 @@ func valueMethodName() string {
17781778
var pc [5]uintptr
17791779
n := runtime.Callers(1, pc[:])
17801780
frames := runtime.CallersFrames(pc[:n])
1781+
valueTyp := TypeOf(Value{})
17811782
var frame runtime.Frame
17821783
for more := true; more; {
17831784
frame, more = frames.Next()
17841785
name := frame.Function
1785-
17861786
// Function name extracted from the call stack can be different from
17871787
// vanilla Go, so is not prefixed by "reflect.Value." as needed by the original.
17881788
// See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/reflect/value.go;l=173-191
1789-
// Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey"
1790-
// into "reflect.Value.SetIterKey".
17911789
// This workaround may become obsolete after
17921790
// https://github.com/gopherjs/gopherjs/issues/1085 is resolved.
17931791

1794-
const prefix = `Object.$packages.reflect.`
1795-
if stringsHasPrefix(name, prefix) {
1796-
if idx := stringsLastIndex(name, '.'); idx >= 0 {
1797-
methodName := name[idx+1:]
1798-
if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' {
1799-
return `reflect.Value.` + methodName
1800-
}
1792+
methodName := name
1793+
if idx := stringsLastIndex(name, '.'); idx >= 0 {
1794+
methodName = name[idx+1:]
1795+
}
1796+
1797+
// Since function name in the call stack doesn't contain receiver name,
1798+
// we are looking for the first exported function name that matches a
1799+
// known Value method.
1800+
if _, ok := valueTyp.MethodByName(methodName); ok {
1801+
if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' {
1802+
return `reflect.Value.` + methodName
18011803
}
18021804
}
18031805
}

0 commit comments

Comments
 (0)