Merge pull request #1335 from nevkontakte/gng7

Final round of refactoring ported from the original generics branch.
diff --git a/compiler/decls.go b/compiler/decls.go
index 36f97d3..c134bc4 100644
--- a/compiler/decls.go
+++ b/compiler/decls.go
@@ -345,7 +345,7 @@
 	}
 
 	d.DceDeps = fc.CollectDCEDeps(func() {
-		d.DeclCode = fc.translateTopLevelFunction(fun, inst)
+		d.DeclCode = fc.namedFuncContext(inst).translateTopLevelFunction(fun)
 	})
 	return d
 }
diff --git a/compiler/expressions.go b/compiler/expressions.go
index fea80b6..5652439 100644
--- a/compiler/expressions.go
+++ b/compiler/expressions.go
@@ -201,7 +201,7 @@
 		}
 
 	case *ast.FuncLit:
-		fun := fc.nestedFunctionContext(fc.pkgCtx.FuncLitInfos[e], exprType.(*types.Signature), typeparams.Instance{}).translateFunctionBody(e.Type, nil, e.Body, "")
+		fun := fc.literalFuncContext(e).translateFunctionBody(e.Type, nil, e.Body)
 		if len(fc.pkgCtx.escapingVars) != 0 {
 			names := make([]string, 0, len(fc.pkgCtx.escapingVars))
 			for obj := range fc.pkgCtx.escapingVars {
@@ -730,10 +730,7 @@
 					}
 				}
 
-				methodName := sel.Obj().Name()
-				if reservedKeywords[methodName] {
-					methodName += "$"
-				}
+				methodName := fc.methodName(sel.Obj().(*types.Func))
 				return fc.translateCall(e, sig, fc.formatExpr("%s.%s", recv, methodName))
 
 			case types.FieldVal:
diff --git a/compiler/functions.go b/compiler/functions.go
index ed3062c..31a9974 100644
--- a/compiler/functions.go
+++ b/compiler/functions.go
@@ -5,6 +5,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"go/ast"
 	"go/types"
@@ -17,18 +18,21 @@
 	"github.com/gopherjs/gopherjs/compiler/typesutil"
 )
 
-// newFunctionContext creates a new nested context for a function corresponding
+// nestedFunctionContext creates a new nested context for a function corresponding
 // to the provided info and instance.
-func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature, inst typeparams.Instance) *funcContext {
+func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, inst typeparams.Instance) *funcContext {
 	if info == nil {
-		panic(fmt.Errorf("missing *analysis.FuncInfo"))
+		panic(errors.New("missing *analysis.FuncInfo"))
 	}
-	if sig == nil {
-		panic(fmt.Errorf("missing *types.Signature"))
+	if inst.Object == nil {
+		panic(errors.New("missing inst.Object"))
 	}
+	o := inst.Object.(*types.Func)
+	sig := o.Type().(*types.Signature)
 
 	c := &funcContext{
 		FuncInfo:     info,
+		instance:     inst,
 		pkgCtx:       fc.pkgCtx,
 		parent:       fc,
 		allVars:      make(map[string]int, len(fc.allVars)),
@@ -53,119 +57,73 @@
 		c.objectNames = map[types.Object]string{}
 	}
 
+	// Synthesize an identifier by which the function may reference itself. Since
+	// it appears in the stack trace, it's useful to include the receiver type in
+	// it.
+	funcRef := o.Name()
+	if recvType := typesutil.RecvType(sig); recvType != nil {
+		funcRef = recvType.Obj().Name() + midDot + funcRef
+	}
+	c.funcRef = c.newVariable(funcRef, true /*pkgLevel*/)
+
+	return c
+}
+
+// namedFuncContext creates a new funcContext for a named Go function
+// (standalone or method).
+func (fc *funcContext) namedFuncContext(inst typeparams.Instance) *funcContext {
+	info := fc.pkgCtx.FuncDeclInfos[inst.Object.(*types.Func)]
+	c := fc.nestedFunctionContext(info, inst)
+
+	return c
+}
+
+// literalFuncContext creates a new funcContext for a function literal. Since
+// go/types doesn't generate *types.Func objects for function literals, we
+// generate a synthetic one for it.
+func (fc *funcContext) literalFuncContext(fun *ast.FuncLit) *funcContext {
+	info := fc.pkgCtx.FuncLitInfos[fun]
+	sig := fc.pkgCtx.TypeOf(fun).(*types.Signature)
+	o := types.NewFunc(fun.Pos(), fc.pkgCtx.Pkg, fc.newLitFuncName(), sig)
+	inst := typeparams.Instance{Object: o}
+
+	c := fc.nestedFunctionContext(info, inst)
 	return c
 }
 
 // translateTopLevelFunction translates a top-level function declaration
-// (standalone function or method) into a corresponding JS function.
+// (standalone function or method) into a corresponding JS function. Must be
+// called on the function context created for the function corresponding instance.
 //
-// Returns a string with a JavaScript statements that define the function or
+// Returns a string with JavaScript statements that define the function or
 // method. For methods it returns declarations for both value- and
 // pointer-receiver (if appropriate).
-func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte {
+func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl) []byte {
 	if fun.Recv == nil {
-		return fc.translateStandaloneFunction(fun, inst)
+		return fc.translateStandaloneFunction(fun)
 	}
 
-	o := inst.Object.(*types.Func)
-	info := fc.pkgCtx.FuncDeclInfos[o]
-
-	sig := o.Type().(*types.Signature)
-	// primaryFunction generates a JS function equivalent of the current Go function
-	// and assigns it to the JS expression defined by lvalue.
-	primaryFunction := func(lvalue string) []byte {
-		if fun.Body == nil {
-			return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName()))
-		}
-
-		var recv *ast.Ident
-		if fun.Recv != nil && fun.Recv.List[0].Names != nil {
-			recv = fun.Recv.List[0].Names[0]
-		}
-		fun := fc.nestedFunctionContext(info, sig, inst).translateFunctionBody(fun.Type, recv, fun.Body, lvalue)
-		return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
-	}
-
-	funName := fun.Name.Name
-	if reservedKeywords[funName] {
-		funName += "$"
-	}
-
-	// proxyFunction generates a JS function that forwards the call to the actual
-	// method implementation for the alternate receiver (e.g. pointer vs
-	// non-pointer).
-	proxyFunction := func(lvalue, receiver string) []byte {
-		fun := fmt.Sprintf("function(...$args) { return %s.%s(...$args); }", receiver, funName)
-		return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
-	}
-
-	recvInst := inst.Recv()
-	recvInstName := fc.instName(recvInst)
-	recvType := recvInst.Object.Type().(*types.Named)
-
-	// Objects the method should be assigned to for the plain and pointer type
-	// of the receiver.
-	prototypeVar := fmt.Sprintf("%s.prototype.%s", recvInstName, funName)
-	ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", recvInstName, funName)
-
-	code := bytes.NewBuffer(nil)
-
-	if _, isStruct := recvType.Underlying().(*types.Struct); isStruct {
-		// Structs are a special case: they are represented by JS objects and their
-		// methods are the underlying object's methods. Due to reference semantics
-		// of the JS variables, the actual backing object is considered to represent
-		// the pointer-to-struct type, and methods are attacher to it first and
-		// foremost.
-		code.Write(primaryFunction(ptrPrototypeVar))
-		code.Write(proxyFunction(prototypeVar, "this.$val"))
-		return code.Bytes()
-	}
-
-	if ptr, isPointer := sig.Recv().Type().(*types.Pointer); isPointer {
-		if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray {
-			// Pointer-to-array is another special case.
-			// TODO(nevkontakte) Find out and document why.
-			code.Write(primaryFunction(prototypeVar))
-			code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", recvInstName)))
-			return code.Bytes()
-		}
-
-		// Methods with pointer-receiver are only attached to the pointer-receiver
-		// type.
-		return primaryFunction(ptrPrototypeVar)
-	}
-
-	// Methods defined for non-pointer receiver are attached to both pointer- and
-	// non-pointer-receiver types.
-	recvExpr := "this.$get()"
-	if isWrapped(recvType) {
-		recvExpr = fmt.Sprintf("new %s(%s)", recvInstName, recvExpr)
-	}
-	code.Write(primaryFunction(prototypeVar))
-	code.Write(proxyFunction(ptrPrototypeVar, recvExpr))
-	return code.Bytes()
+	return fc.translateMethod(fun)
 }
 
 // translateStandaloneFunction translates a package-level function.
 //
-// It returns a JS statements which define the corresponding function in a
+// It returns JS statements which define the corresponding function in a
 // package context. Exported functions are also assigned to the `$pkg` object.
-func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte {
-	o := inst.Object.(*types.Func)
-	info := fc.pkgCtx.FuncDeclInfos[o]
-	sig := o.Type().(*types.Signature)
+func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl) []byte {
+	o := fc.instance.Object.(*types.Func)
 
 	if fun.Recv != nil {
 		panic(fmt.Errorf("expected standalone function, got method: %s", o))
 	}
 
-	lvalue := fc.instName(inst)
+	lvalue := fc.instName(fc.instance)
 
 	if fun.Body == nil {
-		return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName()))
+		return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o)))
 	}
 
-	body := fc.nestedFunctionContext(info, sig, inst).translateFunctionBody(fun.Type, nil, fun.Body, lvalue)
+	body := fc.translateFunctionBody(fun.Type, nil, fun.Body)
 	code := bytes.NewBuffer(nil)
 	fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body)
 	if fun.Name.IsExported() {
@@ -174,12 +132,91 @@
 	return code.Bytes()
 }
 
+// translateMethod translates a named type method.
+//
+// It returns one or more JS statements which define the method. Methods with
+// non-pointer receiver are automatically defined for the pointer-receiver type.
+func (fc *funcContext) translateMethod(fun *ast.FuncDecl) []byte {
+	o := fc.instance.Object.(*types.Func)
+	funName := fc.methodName(o)
+
+	// primaryFunction generates a JS function equivalent of the current Go function
+	// and assigns it to the JS expression defined by lvalue.
+	primaryFunction := func(lvalue string) []byte {
+		if fun.Body == nil {
+			return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fc.unimplementedFunction(o)))
+		}
+
+		var recv *ast.Ident
+		if fun.Recv != nil && fun.Recv.List[0].Names != nil {
+			recv = fun.Recv.List[0].Names[0]
+		}
+		fun := fc.translateFunctionBody(fun.Type, recv, fun.Body)
+		return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
+	}
+
+	recvInst := fc.instance.Recv()
+	recvInstName := fc.instName(recvInst)
+	recvType := recvInst.Object.Type().(*types.Named)
+
+	// Objects the method should be assigned to for the plain and pointer type
+	// of the receiver.
+	prototypeVar := fmt.Sprintf("%s.prototype.%s", recvInstName, funName)
+	ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", recvInstName, funName)
+
+	// Methods with pointer-receiver are only attached to the pointer-receiver type.
+	if _, isPointer := fc.sig.Sig.Recv().Type().(*types.Pointer); isPointer {
+		return primaryFunction(ptrPrototypeVar)
+	}
+
+	// Methods with non-pointer receivers must be defined both for the pointer
+	// and non-pointer types. To minimize generated code size, we generate a
+	// complete implementation for only one receiver (non-pointer for most types)
+	// and define a proxy function on the other, which converts the receiver type
+	// and forwards the call to the primary implementation.
+	proxyFunction := func(lvalue, receiver string) []byte {
+		fun := fmt.Sprintf("function(...$args) { return %s.%s(...$args); }", receiver, funName)
+		return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
+	}
+
+	// Structs are a special case: they are represented by JS objects and their
+	// methods are the underlying object's methods. Due to reference semantics of
+	// the JS variables, the actual backing object is considered to represent the
+	// pointer-to-struct type, and methods are attacher to it first and foremost.
+	if _, isStruct := recvType.Underlying().(*types.Struct); isStruct {
+		code := bytes.Buffer{}
+		code.Write(primaryFunction(ptrPrototypeVar))
+		code.Write(proxyFunction(prototypeVar, "this.$val"))
+		return code.Bytes()
+	}
+
+	// Methods defined for non-pointer receiver are attached to both pointer- and
+	// non-pointer-receiver types.
+	proxyRecvExpr := "this.$get()"
+	if isWrapped(recvType) {
+		proxyRecvExpr = fmt.Sprintf("new %s(%s)", recvInstName, proxyRecvExpr)
+	}
+	code := bytes.Buffer{}
+	code.Write(primaryFunction(prototypeVar))
+	code.Write(proxyFunction(ptrPrototypeVar, proxyRecvExpr))
+	return code.Bytes()
+}
+
+// unimplementedFunction returns a JS function expression for a Go function
+// without a body, which would throw an exception if called.
+//
+// In Go such functions are either used with a //go:linkname directive or with
+// assembler intrinsics, only former of which is supported by GopherJS.
+func (fc *funcContext) unimplementedFunction(o *types.Func) string {
+	return fmt.Sprintf("function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t}", o.FullName())
+}
+
 // translateFunctionBody translates body of a top-level or literal function.
 //
 // It returns a JS function expression that represents the given Go function.
 // Function receiver must have been created with nestedFunctionContext() to have
 // required metadata set up.
-func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string {
+func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt) string {
 	prevEV := fc.pkgCtx.escapingVars
 
 	// Generate a list of function argument variables. Since Go allows nameless
@@ -233,7 +270,7 @@
 
 	sort.Strings(fc.localVars)
 
-	var prefix, suffix, functionName string
+	var prefix, suffix string
 
 	if len(fc.Flattened) != 0 {
 		// $s contains an index of the switch case a blocking function reached
@@ -254,21 +291,19 @@
 	localVarDefs := "" // Function-local var declaration at the top.
 
 	if len(fc.Blocking) != 0 {
-		if funcRef == "" {
-			funcRef = "$b"
-			functionName = " $b"
-		}
-
 		localVars := append([]string{}, fc.localVars...)
 		// There are several special variables involved in handling blocking functions:
 		// $r is sometimes used as a temporary variable to store blocking call result.
 		// $c indicates that a function is being resumed after a blocking call when set to true.
 		// $f is an object used to save and restore function context for blocking calls.
 		localVars = append(localVars, "$r")
+		// funcRef identifies the function object itself, so it doesn't need to be saved
+		// or restored.
+		localVars = removeMatching(localVars, fc.funcRef)
 		// If a blocking function is being resumed, initialize local variables from the saved context.
 		localVarDefs = fmt.Sprintf("var {%s, $c} = $restore(this, {%s});\n", strings.Join(localVars, ", "), strings.Join(args, ", "))
 		// If the function gets blocked, save local variables for future.
-		saveContext := fmt.Sprintf("var $f = {$blk: "+funcRef+", $c: true, $r, %s};", strings.Join(fc.localVars, ", "))
+		saveContext := fmt.Sprintf("var $f = {$blk: "+fc.funcRef+", $c: true, $r, %s};", strings.Join(fc.localVars, ", "))
 
 		suffix = " " + saveContext + "return $f;" + suffix
 	} else if len(fc.localVars) > 0 {
@@ -316,5 +351,5 @@
 
 	fc.pkgCtx.escapingVars = prevEV
 
-	return fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(args, ", "), bodyOutput, fc.Indentation(0))
+	return fmt.Sprintf("function %s(%s) {\n%s%s}", fc.funcRef, strings.Join(args, ", "), bodyOutput, fc.Indentation(0))
 }
diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go
index 47b9366..81f4c7b 100644
--- a/compiler/natives/src/reflect/reflect.go
+++ b/compiler/natives/src/reflect/reflect.go
@@ -1778,26 +1778,28 @@
 	var pc [5]uintptr
 	n := runtime.Callers(1, pc[:])
 	frames := runtime.CallersFrames(pc[:n])
+	valueTyp := TypeOf(Value{})
 	var frame runtime.Frame
 	for more := true; more; {
 		frame, more = frames.Next()
 		name := frame.Function
-
 		// Function name extracted from the call stack can be different from
 		// vanilla Go, so is not prefixed by "reflect.Value." as needed by the original.
 		// See https://cs.opensource.google/go/go/+/refs/tags/go1.19.13:src/reflect/value.go;l=173-191
-		// Here we try to fix stuff like "Object.$packages.reflect.Q.ptr.SetIterKey"
-		// into "reflect.Value.SetIterKey".
 		// This workaround may become obsolete after
 		// https://github.com/gopherjs/gopherjs/issues/1085 is resolved.
 
-		const prefix = `Object.$packages.reflect.`
-		if stringsHasPrefix(name, prefix) {
-			if idx := stringsLastIndex(name, '.'); idx >= 0 {
-				methodName := name[idx+1:]
-				if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' {
-					return `reflect.Value.` + methodName
-				}
+		methodName := name
+		if idx := stringsLastIndex(name, '.'); idx >= 0 {
+			methodName = name[idx+1:]
+		}
+
+		// Since function name in the call stack doesn't contain receiver name,
+		// we are looking for the first exported function name that matches a
+		// known Value method.
+		if _, ok := valueTyp.MethodByName(methodName); ok {
+			if len(methodName) > 0 && 'A' <= methodName[0] && methodName[0] <= 'Z' {
+				return `reflect.Value.` + methodName
 			}
 		}
 	}
diff --git a/compiler/natives/src/reflect/reflect_test.go b/compiler/natives/src/reflect/reflect_test.go
index 79bbe53..4c0bcd0 100644
--- a/compiler/natives/src/reflect/reflect_test.go
+++ b/compiler/natives/src/reflect/reflect_test.go
@@ -298,3 +298,23 @@
 func TestStructOfTooLarge(t *testing.T) {
 	t.Skip("This test is dependent on field alignment to determine if a struct size would exceed virtual address space.")
 }
+
+func TestSetLenCap(t *testing.T) {
+	t.Skip("Test depends on call stack function names: https://github.com/gopherjs/gopherjs/issues/1085")
+}
+
+func TestSetPanic(t *testing.T) {
+	t.Skip("Test depends on call stack function names: https://github.com/gopherjs/gopherjs/issues/1085")
+}
+
+func TestCallPanic(t *testing.T) {
+	t.Skip("Test depends on call stack function names: https://github.com/gopherjs/gopherjs/issues/1085")
+}
+
+func TestValuePanic(t *testing.T) {
+	t.Skip("Test depends on call stack function names: https://github.com/gopherjs/gopherjs/issues/1085")
+}
+
+func TestSetIter(t *testing.T) {
+	t.Skip("Test depends on call stack function names: https://github.com/gopherjs/gopherjs/issues/1085")
+}
diff --git a/compiler/package.go b/compiler/package.go
index 34387b5..bcdfca5 100644
--- a/compiler/package.go
+++ b/compiler/package.go
@@ -53,6 +53,15 @@
 // JavaScript code (as defined for `var` declarations).
 type funcContext struct {
 	*analysis.FuncInfo
+	// Function instance this context corresponds to, or zero if the context is
+	// top-level or doesn't correspond to a function. For function literals, this
+	// is a synthetic object that assigns a unique identity to the function.
+	instance typeparams.Instance
+	// JavaScript identifier assigned to the function object (the word after the
+	// "function" keyword in the generated code). This identifier can be used
+	// within the function scope to reference the function object. It will also
+	// appear in the stack trace.
+	funcRef string
 	// Surrounding package context.
 	pkgCtx *pkgContext
 	// Function context, surrounding this function definition. For package-level
@@ -104,6 +113,8 @@
 	typeResolver *typeparams.Resolver
 	// Mapping from function-level objects to JS variable names they have been assigned.
 	objectNames map[types.Object]string
+	// Number of function literals encountered within the current function context.
+	funcLitCounter int
 }
 
 func newRootCtx(tContext *types.Context, srcs sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool, minify bool) *funcContext {
diff --git a/compiler/utils.go b/compiler/utils.go
index 7fec5b2..62c09a0 100644
--- a/compiler/utils.go
+++ b/compiler/utils.go
@@ -23,6 +23,11 @@
 	"github.com/gopherjs/gopherjs/compiler/typesutil"
 )
 
+// We use this character as a separator in synthetic identifiers instead of a
+// regular dot. This character is safe for use in JS identifiers and helps to
+// visually separate components of the name when it appears in a stack trace.
+const midDot = "·"
+
 // root returns the topmost function context corresponding to the package scope.
 func (fc *funcContext) root() *funcContext {
 	if fc.isRoot() {
@@ -376,6 +381,25 @@
 	return ident
 }
 
+// newLitFuncName generates a new synthetic name for a function literal.
+func (fc *funcContext) newLitFuncName() string {
+	fc.funcLitCounter++
+	name := &strings.Builder{}
+
+	// If function literal is defined inside another function, qualify its
+	// synthetic name with the outer function to make it easier to identify.
+	if fc.instance.Object != nil {
+		if recvType := typesutil.RecvType(fc.sig.Sig); recvType != nil {
+			name.WriteString(recvType.Obj().Name())
+			name.WriteString(midDot)
+		}
+		name.WriteString(fc.instance.Object.Name())
+		name.WriteString(midDot)
+	}
+	fmt.Fprintf(name, "func%d", fc.funcLitCounter)
+	return name.String()
+}
+
 func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr {
 	fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t}
 	return e
@@ -474,6 +498,21 @@
 	return fmt.Sprintf("%s[%d /* %v */]", objName, fc.pkgCtx.instanceSet.ID(inst), inst.TArgs)
 }
 
+// methodName returns a JS identifier (specifically, object property name)
+// corresponding to the given method.
+func (fc *funcContext) methodName(fun *types.Func) string {
+	if fun.Type().(*types.Signature).Recv() == nil {
+		panic(fmt.Errorf("expected a method, got a standalone function %v", fun))
+	}
+	name := fun.Name()
+	// Method names are scoped to their receiver type and guaranteed to be
+	// unique within that, so we only need to make sure it's not a reserved keyword
+	if reservedKeywords[name] {
+		name += "$"
+	}
+	return name
+}
+
 func (fc *funcContext) varPtrName(o *types.Var) string {
 	if isPkgLevel(o) && o.Exported() {
 		return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr"
@@ -894,7 +933,15 @@
 }
 
 func encodeIdent(name string) string {
-	return strings.Replace(url.QueryEscape(name), "%", "$", -1)
+	// Quick-and-dirty way to make any string safe for use as an identifier in JS.
+	name = url.QueryEscape(name)
+	// We use unicode middle dot as a visual separator in synthetic identifiers.
+	// It is safe for use in a JS identifier, so we un-encode it for readability.
+	name = strings.ReplaceAll(name, "%C2%B7", midDot)
+	// QueryEscape uses '%' before hex-codes of escaped characters, which is not
+	// allowed in a JS identifier, use '$' instead.
+	name = strings.ReplaceAll(name, "%", "$")
+	return name
 }
 
 // formatJSStructTagVal returns JavaScript code for accessing an object's property
@@ -980,3 +1027,13 @@
 	fe, ok := err.(*FatalError)
 	return fe, ok
 }
+
+func removeMatching[T comparable](haystack []T, needle T) []T {
+	var result []T
+	for _, el := range haystack {
+		if el != needle {
+			result = append(result, el)
+		}
+	}
+	return result
+}