github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/utils.go (about)

     1  package compiler
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"go/ast"
     9  	"go/constant"
    10  	"go/token"
    11  	"go/types"
    12  	"net/url"
    13  	"regexp"
    14  	"runtime/debug"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	"text/template"
    19  	"unicode"
    20  
    21  	"github.com/gopherjs/gopherjs/compiler/analysis"
    22  	"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
    23  	"github.com/gopherjs/gopherjs/compiler/typesutil"
    24  )
    25  
    26  // root returns the topmost function context corresponding to the package scope.
    27  func (fc *funcContext) root() *funcContext {
    28  	if fc.parent == nil {
    29  		return fc
    30  	}
    31  	return fc.parent.root()
    32  }
    33  
    34  func (fc *funcContext) Write(b []byte) (int, error) {
    35  	fc.writePos()
    36  	fc.output = append(fc.output, b...)
    37  	return len(b), nil
    38  }
    39  
    40  func (fc *funcContext) Printf(format string, values ...interface{}) {
    41  	fc.Write([]byte(fc.Indentation(0)))
    42  	fmt.Fprintf(fc, format, values...)
    43  	fc.Write([]byte{'\n'})
    44  	fc.Write(fc.delayedOutput)
    45  	fc.delayedOutput = nil
    46  }
    47  
    48  func (fc *funcContext) PrintCond(cond bool, onTrue, onFalse string) {
    49  	if !cond {
    50  		fc.Printf("/* %s */ %s", strings.Replace(onTrue, "*/", "<star>/", -1), onFalse)
    51  		return
    52  	}
    53  	fc.Printf("%s", onTrue)
    54  }
    55  
    56  func (fc *funcContext) SetPos(pos token.Pos) {
    57  	fc.posAvailable = true
    58  	fc.pos = pos
    59  }
    60  
    61  func (fc *funcContext) writePos() {
    62  	if fc.posAvailable {
    63  		fc.posAvailable = false
    64  		fc.Write([]byte{'\b'})
    65  		binary.Write(fc, binary.BigEndian, uint32(fc.pos))
    66  	}
    67  }
    68  
    69  // Indented increases generated code indentation level by 1 for the code emitted
    70  // from the callback f.
    71  func (fc *funcContext) Indented(f func()) {
    72  	fc.pkgCtx.indentation++
    73  	f()
    74  	fc.pkgCtx.indentation--
    75  }
    76  
    77  // Indentation returns a sequence of "\t" characters appropriate to the current
    78  // generated code indentation level. The `extra` parameter provides relative
    79  // indentation adjustment.
    80  func (fc *funcContext) Indentation(extra int) string {
    81  	return strings.Repeat("\t", fc.pkgCtx.indentation+extra)
    82  }
    83  
    84  func (fc *funcContext) CatchOutput(indent int, f func()) []byte {
    85  	origoutput := fc.output
    86  	fc.output = nil
    87  	fc.pkgCtx.indentation += indent
    88  	f()
    89  	fc.writePos()
    90  	caught := fc.output
    91  	fc.output = origoutput
    92  	fc.pkgCtx.indentation -= indent
    93  	return caught
    94  }
    95  
    96  func (fc *funcContext) Delayed(f func()) {
    97  	fc.delayedOutput = fc.CatchOutput(0, f)
    98  }
    99  
   100  // expandTupleArgs converts a function call which argument is a tuple returned
   101  // by another function into a set of individual call arguments corresponding to
   102  // tuple elements.
   103  //
   104  // For example, for functions defined as:
   105  //
   106  //	func a() (int, string) {return 42, "foo"}
   107  //	func b(a1 int, a2 string) {}
   108  //
   109  // ...the following statement:
   110  //
   111  //	b(a())
   112  //
   113  // ...will be transformed into:
   114  //
   115  //	_tuple := a()
   116  //	b(_tuple[0], _tuple[1])
   117  func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr {
   118  	if len(argExprs) != 1 {
   119  		return argExprs
   120  	}
   121  
   122  	tuple, isTuple := fc.typeOf(argExprs[0]).(*types.Tuple)
   123  	if !isTuple {
   124  		return argExprs
   125  	}
   126  
   127  	tupleVar := fc.newLocalVariable("_tuple")
   128  	fc.Printf("%s = %s;", tupleVar, fc.translateExpr(argExprs[0]))
   129  	argExprs = make([]ast.Expr, tuple.Len())
   130  	for i := range argExprs {
   131  		argExprs[i] = fc.newIdent(fc.formatExpr("%s[%d]", tupleVar, i).String(), tuple.At(i).Type())
   132  	}
   133  	return argExprs
   134  }
   135  
   136  func (fc *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, ellipsis bool) []string {
   137  	argExprs = fc.expandTupleArgs(argExprs)
   138  
   139  	sigTypes := typesutil.Signature{Sig: sig}
   140  
   141  	if sig.Variadic() && len(argExprs) == 0 {
   142  		return []string{fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType()))}
   143  	}
   144  
   145  	preserveOrder := false
   146  	for i := 1; i < len(argExprs); i++ {
   147  		preserveOrder = preserveOrder || fc.Blocking[argExprs[i]]
   148  	}
   149  
   150  	args := make([]string, len(argExprs))
   151  	for i, argExpr := range argExprs {
   152  		arg := fc.translateImplicitConversionWithCloning(argExpr, sigTypes.Param(i, ellipsis)).String()
   153  
   154  		if preserveOrder && fc.pkgCtx.Types[argExpr].Value == nil {
   155  			argVar := fc.newLocalVariable("_arg")
   156  			fc.Printf("%s = %s;", argVar, arg)
   157  			arg = argVar
   158  		}
   159  
   160  		args[i] = arg
   161  	}
   162  
   163  	// If variadic arguments were passed in as individual elements, regroup them
   164  	// into a slice and pass it as a single argument.
   165  	if sig.Variadic() && !ellipsis {
   166  		required := args[:sigTypes.RequiredParams()]
   167  		var variadic string
   168  		if len(args) == sigTypes.RequiredParams() {
   169  			// If no variadic parameters were passed, the slice value defaults to nil.
   170  			variadic = fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType()))
   171  		} else {
   172  			variadic = fmt.Sprintf("new %s([%s])", fc.typeName(sigTypes.VariadicType()), strings.Join(args[sigTypes.RequiredParams():], ", "))
   173  		}
   174  		return append(required, variadic)
   175  	}
   176  	return args
   177  }
   178  
   179  func (fc *funcContext) translateSelection(sel typesutil.Selection, pos token.Pos) ([]string, string) {
   180  	var fields []string
   181  	t := sel.Recv()
   182  	for _, index := range sel.Index() {
   183  		if ptr, isPtr := t.Underlying().(*types.Pointer); isPtr {
   184  			t = ptr.Elem()
   185  		}
   186  		s := t.Underlying().(*types.Struct)
   187  		if jsTag := getJsTag(s.Tag(index)); jsTag != "" {
   188  			jsFieldName := s.Field(index).Name()
   189  			for {
   190  				fields = append(fields, fieldName(s, 0))
   191  				ft := s.Field(0).Type()
   192  				if typesutil.IsJsObject(ft) {
   193  					return fields, jsTag
   194  				}
   195  				ft = ft.Underlying()
   196  				if ptr, ok := ft.(*types.Pointer); ok {
   197  					ft = ptr.Elem().Underlying()
   198  				}
   199  				var ok bool
   200  				s, ok = ft.(*types.Struct)
   201  				if !ok || s.NumFields() == 0 {
   202  					fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: pos, Msg: fmt.Sprintf("could not find field with type *js.Object for 'js' tag of field '%s'", jsFieldName), Soft: true})
   203  					return nil, ""
   204  				}
   205  			}
   206  		}
   207  		fields = append(fields, fieldName(s, index))
   208  		t = s.Field(index).Type()
   209  	}
   210  	return fields, ""
   211  }
   212  
   213  var nilObj = types.Universe.Lookup("nil")
   214  
   215  func (fc *funcContext) zeroValue(ty types.Type) ast.Expr {
   216  	switch t := ty.Underlying().(type) {
   217  	case *types.Basic:
   218  		switch {
   219  		case isBoolean(t):
   220  			return fc.newConst(ty, constant.MakeBool(false))
   221  		case isNumeric(t):
   222  			return fc.newConst(ty, constant.MakeInt64(0))
   223  		case isString(t):
   224  			return fc.newConst(ty, constant.MakeString(""))
   225  		case t.Kind() == types.UnsafePointer:
   226  			// fall through to "nil"
   227  		case t.Kind() == types.UntypedNil:
   228  			panic("Zero value for untyped nil.")
   229  		default:
   230  			panic(fmt.Sprintf("Unhandled basic type: %v\n", t))
   231  		}
   232  	case *types.Array, *types.Struct:
   233  		return fc.setType(&ast.CompositeLit{}, ty)
   234  	case *types.Chan, *types.Interface, *types.Map, *types.Signature, *types.Slice, *types.Pointer:
   235  		// fall through to "nil"
   236  	default:
   237  		panic(fmt.Sprintf("Unhandled type: %T\n", t))
   238  	}
   239  	id := fc.newIdent("nil", ty)
   240  	fc.pkgCtx.Uses[id] = nilObj
   241  	return id
   242  }
   243  
   244  func (fc *funcContext) newConst(t types.Type, value constant.Value) ast.Expr {
   245  	id := &ast.Ident{}
   246  	fc.pkgCtx.Types[id] = types.TypeAndValue{Type: t, Value: value}
   247  	return id
   248  }
   249  
   250  // newLocalVariable assigns a new JavaScript variable name for the given Go
   251  // local variable name. In this context "local" means "in scope of the current"
   252  // functionContext.
   253  func (fc *funcContext) newLocalVariable(name string) string {
   254  	return fc.newVariable(name, false)
   255  }
   256  
   257  // newVariable assigns a new JavaScript variable name for the given Go variable
   258  // or type.
   259  //
   260  // If there is already a variable with the same name visible in the current
   261  // function context (e.g. due to shadowing), the returned name will be suffixed
   262  // with a number to prevent conflict. This is necessary because Go name
   263  // resolution scopes differ from var declarations in JS.
   264  //
   265  // If pkgLevel is true, the variable is declared at the package level and added
   266  // to this functionContext, as well as all parents, but not to the list of local
   267  // variables. If false, it is added to this context only, as well as the list of
   268  // local vars.
   269  func (fc *funcContext) newVariable(name string, pkgLevel bool) string {
   270  	if name == "" {
   271  		panic("newVariable: empty name")
   272  	}
   273  	name = encodeIdent(name)
   274  	if fc.pkgCtx.minify {
   275  		i := 0
   276  		for {
   277  			offset := int('a')
   278  			if pkgLevel {
   279  				offset = int('A')
   280  			}
   281  			j := i
   282  			name = ""
   283  			for {
   284  				name = string(rune(offset+(j%26))) + name
   285  				j = j/26 - 1
   286  				if j == -1 {
   287  					break
   288  				}
   289  			}
   290  			if fc.allVars[name] == 0 {
   291  				break
   292  			}
   293  			i++
   294  		}
   295  	}
   296  	n := fc.allVars[name]
   297  	fc.allVars[name] = n + 1
   298  	varName := name
   299  	if n > 0 {
   300  		varName = fmt.Sprintf("%s$%d", name, n)
   301  	}
   302  
   303  	if pkgLevel {
   304  		for c2 := fc.parent; c2 != nil; c2 = c2.parent {
   305  			c2.allVars[name] = n + 1
   306  		}
   307  		return varName
   308  	}
   309  
   310  	fc.localVars = append(fc.localVars, varName)
   311  	return varName
   312  }
   313  
   314  func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident {
   315  	ident := ast.NewIdent(name)
   316  	fc.setType(ident, t)
   317  	obj := types.NewVar(0, fc.pkgCtx.Pkg, name, t)
   318  	fc.pkgCtx.Uses[ident] = obj
   319  	fc.objectNames[obj] = name
   320  	return ident
   321  }
   322  
   323  func (fc *funcContext) newTypeIdent(name string, obj types.Object) *ast.Ident {
   324  	ident := ast.NewIdent(name)
   325  	fc.pkgCtx.Info.Uses[ident] = obj
   326  	return ident
   327  }
   328  
   329  func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr {
   330  	fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t}
   331  	return e
   332  }
   333  
   334  func (fc *funcContext) pkgVar(pkg *types.Package) string {
   335  	if pkg == fc.pkgCtx.Pkg {
   336  		return "$pkg"
   337  	}
   338  
   339  	pkgVar, found := fc.pkgCtx.pkgVars[pkg.Path()]
   340  	if !found {
   341  		pkgVar = fmt.Sprintf(`$packages["%s"]`, pkg.Path())
   342  	}
   343  	return pkgVar
   344  }
   345  
   346  func isVarOrConst(o types.Object) bool {
   347  	switch o.(type) {
   348  	case *types.Var, *types.Const:
   349  		return true
   350  	}
   351  	return false
   352  }
   353  
   354  func isPkgLevel(o types.Object) bool {
   355  	// Note: named types are always assigned a variable at package level to be
   356  	// initialized with the rest of the package types, even the types declared
   357  	// in a statement inside a function.
   358  	_, isType := o.(*types.TypeName)
   359  	return (o.Parent() != nil && o.Parent().Parent() == types.Universe) || isType
   360  }
   361  
   362  // assignedObjectName checks if the object has been previously assigned a name
   363  // in this or one of the parent contexts. If not, found will be false.
   364  func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bool) {
   365  	if fc == nil {
   366  		return "", false
   367  	}
   368  	if name, found := fc.parent.assignedObjectName(o); found {
   369  		return name, true
   370  	}
   371  
   372  	name, found = fc.objectNames[o]
   373  	return name, found
   374  }
   375  
   376  // objectName returns a JS expression that refers to the given object. If the
   377  // object hasn't been previously assigned a JS variable name, it will be
   378  // allocated as needed.
   379  func (fc *funcContext) objectName(o types.Object) string {
   380  	if isPkgLevel(o) {
   381  		fc.pkgCtx.dependencies[o] = true
   382  
   383  		if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) {
   384  			return fc.pkgVar(o.Pkg()) + "." + o.Name()
   385  		}
   386  	}
   387  
   388  	name, ok := fc.assignedObjectName(o)
   389  	if !ok {
   390  		pkgLevel := isPkgLevel(o)
   391  		name = fc.newVariable(o.Name(), pkgLevel)
   392  		if pkgLevel {
   393  			fc.root().objectNames[o] = name
   394  		} else {
   395  			fc.objectNames[o] = name
   396  		}
   397  	}
   398  
   399  	if v, ok := o.(*types.Var); ok && fc.pkgCtx.escapingVars[v] {
   400  		return name + "[0]"
   401  	}
   402  	return name
   403  }
   404  
   405  // instName returns a JS expression that refers to the provided instance of a
   406  // function or type. Non-generic objects may be represented as an instance with
   407  // zero type arguments.
   408  func (fc *funcContext) instName(inst typeparams.Instance) string {
   409  	objName := fc.objectName(inst.Object)
   410  	if inst.IsTrivial() {
   411  		return objName
   412  	}
   413  	return fmt.Sprintf("%s[%d /* %v */]", objName, fc.pkgCtx.instanceSet.ID(inst), inst.TArgs)
   414  }
   415  
   416  func (fc *funcContext) varPtrName(o *types.Var) string {
   417  	if isPkgLevel(o) && o.Exported() {
   418  		return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr"
   419  	}
   420  
   421  	name, ok := fc.pkgCtx.varPtrNames[o]
   422  	if !ok {
   423  		name = fc.newVariable(o.Name()+"$ptr", isPkgLevel(o))
   424  		fc.pkgCtx.varPtrNames[o] = name
   425  	}
   426  	return name
   427  }
   428  
   429  // typeName returns a JS identifier name for the given Go type.
   430  //
   431  // For the built-in types it returns identifiers declared in the prelude. For
   432  // all user-defined or composite types it creates a unique JS identifier and
   433  // will return it on all subsequent calls for the type.
   434  func (fc *funcContext) typeName(ty types.Type) string {
   435  	switch t := ty.(type) {
   436  	case *types.Basic:
   437  		return "$" + toJavaScriptType(t)
   438  	case *types.Named:
   439  		if t.Obj().Name() == "error" {
   440  			return "$error"
   441  		}
   442  		inst := typeparams.Instance{Object: t.Obj()}
   443  		for i := 0; i < t.TypeArgs().Len(); i++ {
   444  			inst.TArgs = append(inst.TArgs, t.TypeArgs().At(i))
   445  		}
   446  		return fc.instName(inst)
   447  	case *types.Interface:
   448  		if t.Empty() {
   449  			return "$emptyInterface"
   450  		}
   451  	}
   452  
   453  	// For anonymous composite types, generate a synthetic package-level type
   454  	// declaration, which will be reused for all instances of this time. This
   455  	// improves performance, since runtime won't have to synthesize the same type
   456  	// repeatedly.
   457  	anonType, ok := fc.pkgCtx.anonTypeMap.At(ty).(*types.TypeName)
   458  	if !ok {
   459  		fc.initArgs(ty) // cause all embedded types to be registered
   460  		varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", true)
   461  		anonType = types.NewTypeName(token.NoPos, fc.pkgCtx.Pkg, varName, ty) // fake types.TypeName
   462  		fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType)
   463  		fc.pkgCtx.anonTypeMap.Set(ty, anonType)
   464  	}
   465  	fc.pkgCtx.dependencies[anonType] = true
   466  	return anonType.Name()
   467  }
   468  
   469  // instanceOf constructs an instance description of the object the ident is
   470  // referring to. For non-generic objects, it will return a trivial instance with
   471  // no type arguments.
   472  func (fc *funcContext) instanceOf(ident *ast.Ident) typeparams.Instance {
   473  	inst := typeparams.Instance{Object: fc.pkgCtx.ObjectOf(ident)}
   474  	if i, ok := fc.pkgCtx.Instances[ident]; ok {
   475  		inst.TArgs = fc.typeResolver.SubstituteAll(i.TypeArgs)
   476  	}
   477  	return inst
   478  }
   479  
   480  // typeOf returns a type associated with the given AST expression. For types
   481  // defined in terms of type parameters, it will substitute type parameters with
   482  // concrete types from the current set of type arguments.
   483  func (fc *funcContext) typeOf(expr ast.Expr) types.Type {
   484  	typ := fc.pkgCtx.TypeOf(expr)
   485  	// If the expression is referring to an instance of a generic type or function,
   486  	// we want the instantiated type.
   487  	if ident, ok := expr.(*ast.Ident); ok {
   488  		if inst, ok := fc.pkgCtx.Instances[ident]; ok {
   489  			typ = inst.Type
   490  		}
   491  	}
   492  	return fc.typeResolver.Substitute(typ)
   493  }
   494  
   495  func (fc *funcContext) selectionOf(e *ast.SelectorExpr) (typesutil.Selection, bool) {
   496  	if sel, ok := fc.pkgCtx.Selections[e]; ok {
   497  		return fc.typeResolver.SubstituteSelection(sel), true
   498  	}
   499  	if sel, ok := fc.pkgCtx.additionalSelections[e]; ok {
   500  		return sel, true
   501  	}
   502  	return nil, false
   503  }
   504  
   505  func (fc *funcContext) externalize(s string, t types.Type) string {
   506  	if typesutil.IsJsObject(t) {
   507  		return s
   508  	}
   509  	switch u := t.Underlying().(type) {
   510  	case *types.Basic:
   511  		if isNumeric(u) && !is64Bit(u) && !isComplex(u) {
   512  			return s
   513  		}
   514  		if u.Kind() == types.UntypedNil {
   515  			return "null"
   516  		}
   517  	}
   518  	return fmt.Sprintf("$externalize(%s, %s)", s, fc.typeName(t))
   519  }
   520  
   521  func (fc *funcContext) handleEscapingVars(n ast.Node) {
   522  	newEscapingVars := make(map[*types.Var]bool)
   523  	for escaping := range fc.pkgCtx.escapingVars {
   524  		newEscapingVars[escaping] = true
   525  	}
   526  	fc.pkgCtx.escapingVars = newEscapingVars
   527  
   528  	var names []string
   529  	objs := analysis.EscapingObjects(n, fc.pkgCtx.Info.Info)
   530  	for _, obj := range objs {
   531  		names = append(names, fc.objectName(obj))
   532  		fc.pkgCtx.escapingVars[obj] = true
   533  	}
   534  	sort.Strings(names)
   535  	for _, name := range names {
   536  		fc.Printf("%s = [%s];", name, name)
   537  	}
   538  }
   539  
   540  func fieldName(t *types.Struct, i int) string {
   541  	name := t.Field(i).Name()
   542  	if name == "_" || reservedKeywords[name] {
   543  		return fmt.Sprintf("%s$%d", name, i)
   544  	}
   545  	return name
   546  }
   547  
   548  func typeKind(ty types.Type) string {
   549  	switch t := ty.Underlying().(type) {
   550  	case *types.Basic:
   551  		return "$kind" + toJavaScriptType(t)
   552  	case *types.Array:
   553  		return "$kindArray"
   554  	case *types.Chan:
   555  		return "$kindChan"
   556  	case *types.Interface:
   557  		return "$kindInterface"
   558  	case *types.Map:
   559  		return "$kindMap"
   560  	case *types.Signature:
   561  		return "$kindFunc"
   562  	case *types.Slice:
   563  		return "$kindSlice"
   564  	case *types.Struct:
   565  		return "$kindStruct"
   566  	case *types.Pointer:
   567  		return "$kindPtr"
   568  	default:
   569  		panic(fmt.Sprintf("Unhandled type: %T\n", t))
   570  	}
   571  }
   572  
   573  func toJavaScriptType(t *types.Basic) string {
   574  	switch t.Kind() {
   575  	case types.UntypedInt:
   576  		return "Int"
   577  	case types.Byte:
   578  		return "Uint8"
   579  	case types.Rune:
   580  		return "Int32"
   581  	case types.UnsafePointer:
   582  		return "UnsafePointer"
   583  	default:
   584  		name := t.String()
   585  		return strings.ToUpper(name[:1]) + name[1:]
   586  	}
   587  }
   588  
   589  func is64Bit(t *types.Basic) bool {
   590  	return t.Kind() == types.Int64 || t.Kind() == types.Uint64
   591  }
   592  
   593  func isBoolean(t *types.Basic) bool {
   594  	return t.Info()&types.IsBoolean != 0
   595  }
   596  
   597  func isComplex(t *types.Basic) bool {
   598  	return t.Info()&types.IsComplex != 0
   599  }
   600  
   601  func isFloat(t *types.Basic) bool {
   602  	return t.Info()&types.IsFloat != 0
   603  }
   604  
   605  func isInteger(t *types.Basic) bool {
   606  	return t.Info()&types.IsInteger != 0
   607  }
   608  
   609  func isNumeric(t *types.Basic) bool {
   610  	return t.Info()&types.IsNumeric != 0
   611  }
   612  
   613  func isString(t *types.Basic) bool {
   614  	return t.Info()&types.IsString != 0
   615  }
   616  
   617  func isUnsigned(t *types.Basic) bool {
   618  	return t.Info()&types.IsUnsigned != 0
   619  }
   620  
   621  func isBlank(expr ast.Expr) bool {
   622  	if expr == nil {
   623  		return true
   624  	}
   625  	if id, isIdent := expr.(*ast.Ident); isIdent {
   626  		return id.Name == "_"
   627  	}
   628  	return false
   629  }
   630  
   631  // isWrapped returns true for types that may need to be boxed to access full
   632  // functionality of the Go type.
   633  //
   634  // For efficiency or interoperability reasons certain Go types can be represented
   635  // by JavaScript values that weren't constructed by the corresponding Go type
   636  // constructor.
   637  //
   638  // For example, consider a Go type:
   639  //
   640  //	type SecretInt int
   641  //	func (_ SecretInt) String() string { return "<secret>" }
   642  //
   643  //	func main() {
   644  //	  var i SecretInt = 1
   645  //	  println(i.String())
   646  //	}
   647  //
   648  // For this example the compiler will generate code similar to the snippet below:
   649  //
   650  //	SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null);
   651  //	SecretInt.prototype.String = function() {
   652  //	  return "<secret>";
   653  //	};
   654  //	main = function() {
   655  //	  var i = 1;
   656  //	  console.log(new SecretInt(i).String());
   657  //	};
   658  //
   659  // Note that the generated code assigns a primitive "number" value into i, and
   660  // only boxes it into an object when it's necessary to access its methods.
   661  func isWrapped(ty types.Type) bool {
   662  	switch t := ty.Underlying().(type) {
   663  	case *types.Basic:
   664  		return !is64Bit(t) && !isComplex(t) && t.Kind() != types.UntypedNil
   665  	case *types.Array, *types.Chan, *types.Map, *types.Signature:
   666  		return true
   667  	case *types.Pointer:
   668  		_, isArray := t.Elem().Underlying().(*types.Array)
   669  		return isArray
   670  	}
   671  	return false
   672  }
   673  
   674  func encodeString(s string) string {
   675  	buffer := bytes.NewBuffer(nil)
   676  	for _, r := range []byte(s) {
   677  		switch r {
   678  		case '\b':
   679  			buffer.WriteString(`\b`)
   680  		case '\f':
   681  			buffer.WriteString(`\f`)
   682  		case '\n':
   683  			buffer.WriteString(`\n`)
   684  		case '\r':
   685  			buffer.WriteString(`\r`)
   686  		case '\t':
   687  			buffer.WriteString(`\t`)
   688  		case '\v':
   689  			buffer.WriteString(`\v`)
   690  		case '"':
   691  			buffer.WriteString(`\"`)
   692  		case '\\':
   693  			buffer.WriteString(`\\`)
   694  		default:
   695  			if r < 0x20 || r > 0x7E {
   696  				fmt.Fprintf(buffer, `\x%02X`, r)
   697  				continue
   698  			}
   699  			buffer.WriteByte(r)
   700  		}
   701  	}
   702  	return `"` + buffer.String() + `"`
   703  }
   704  
   705  func getJsTag(tag string) string {
   706  	for tag != "" {
   707  		// skip leading space
   708  		i := 0
   709  		for i < len(tag) && tag[i] == ' ' {
   710  			i++
   711  		}
   712  		tag = tag[i:]
   713  		if tag == "" {
   714  			break
   715  		}
   716  
   717  		// scan to colon.
   718  		// a space or a quote is a syntax error
   719  		i = 0
   720  		for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' {
   721  			i++
   722  		}
   723  		if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
   724  			break
   725  		}
   726  		name := string(tag[:i])
   727  		tag = tag[i+1:]
   728  
   729  		// scan quoted string to find value
   730  		i = 1
   731  		for i < len(tag) && tag[i] != '"' {
   732  			if tag[i] == '\\' {
   733  				i++
   734  			}
   735  			i++
   736  		}
   737  		if i >= len(tag) {
   738  			break
   739  		}
   740  		qvalue := string(tag[:i+1])
   741  		tag = tag[i+1:]
   742  
   743  		if name == "js" {
   744  			value, _ := strconv.Unquote(qvalue)
   745  			return value
   746  		}
   747  	}
   748  	return ""
   749  }
   750  
   751  func needsSpace(c byte) bool {
   752  	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$'
   753  }
   754  
   755  func removeWhitespace(b []byte, minify bool) []byte {
   756  	if !minify {
   757  		return b
   758  	}
   759  
   760  	var out []byte
   761  	var previous byte
   762  	for len(b) > 0 {
   763  		switch b[0] {
   764  		case '\b':
   765  			out = append(out, b[:5]...)
   766  			b = b[5:]
   767  			continue
   768  		case ' ', '\t', '\n':
   769  			if (!needsSpace(previous) || !needsSpace(b[1])) && !(previous == '-' && b[1] == '-') {
   770  				b = b[1:]
   771  				continue
   772  			}
   773  		case '"':
   774  			out = append(out, '"')
   775  			b = b[1:]
   776  			for {
   777  				i := bytes.IndexAny(b, "\"\\")
   778  				out = append(out, b[:i]...)
   779  				b = b[i:]
   780  				if b[0] == '"' {
   781  					break
   782  				}
   783  				// backslash
   784  				out = append(out, b[:2]...)
   785  				b = b[2:]
   786  			}
   787  		case '/':
   788  			if b[1] == '*' {
   789  				i := bytes.Index(b[2:], []byte("*/"))
   790  				b = b[i+4:]
   791  				continue
   792  			}
   793  		}
   794  		out = append(out, b[0])
   795  		previous = b[0]
   796  		b = b[1:]
   797  	}
   798  	return out
   799  }
   800  
   801  func rangeCheck(pattern string, constantIndex, array bool) string {
   802  	if constantIndex && array {
   803  		return pattern
   804  	}
   805  	lengthProp := "$length"
   806  	if array {
   807  		lengthProp = "length"
   808  	}
   809  	check := "%2f >= %1e." + lengthProp
   810  	if !constantIndex {
   811  		check = "(%2f < 0 || " + check + ")"
   812  	}
   813  	return "(" + check + ` ? ($throwRuntimeError("index out of range"), undefined) : ` + pattern + ")"
   814  }
   815  
   816  func encodeIdent(name string) string {
   817  	return strings.Replace(url.QueryEscape(name), "%", "$", -1)
   818  }
   819  
   820  // formatJSStructTagVal returns JavaScript code for accessing an object's property
   821  // identified by jsTag. It prefers the dot notation over the bracket notation when
   822  // possible, since the dot notation produces slightly smaller output.
   823  //
   824  // For example:
   825  //
   826  //	"my_name" -> ".my_name"
   827  //	"my name" -> `["my name"]`
   828  //
   829  // For more information about JavaScript property accessors and identifiers, see
   830  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors and
   831  // https://developer.mozilla.org/en-US/docs/Glossary/Identifier.
   832  func formatJSStructTagVal(jsTag string) string {
   833  	for i, r := range jsTag {
   834  		ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_'
   835  		if !ok {
   836  			// Saw an invalid JavaScript identifier character,
   837  			// so use bracket notation.
   838  			return `["` + template.JSEscapeString(jsTag) + `"]`
   839  		}
   840  	}
   841  	// Safe to use dot notation without any escaping.
   842  	return "." + jsTag
   843  }
   844  
   845  // ErrorAt annotates an error with a position in the source code.
   846  func ErrorAt(err error, fset *token.FileSet, pos token.Pos) error {
   847  	return fmt.Errorf("%s: %w", fset.Position(pos), err)
   848  }
   849  
   850  // FatalError is an error compiler panics with when it encountered a fatal error.
   851  //
   852  // FatalError implements io.Writer, which can be used to record any free-form
   853  // debugging details for human consumption. This information will be included
   854  // into String() result along with the rest.
   855  type FatalError struct {
   856  	cause interface{}
   857  	stack []byte
   858  	clues strings.Builder
   859  }
   860  
   861  func (b FatalError) Unwrap() error {
   862  	if b.cause == nil {
   863  		return nil
   864  	}
   865  	if err, ok := b.cause.(error); ok {
   866  		return err
   867  	}
   868  	if s, ok := b.cause.(string); ok {
   869  		return errors.New(s)
   870  	}
   871  	return fmt.Errorf("[%T]: %v", b.cause, b.cause)
   872  }
   873  
   874  // Write implements io.Writer and can be used to store free-form debugging clues.
   875  func (b *FatalError) Write(p []byte) (n int, err error) { return b.clues.Write(p) }
   876  
   877  func (b FatalError) Error() string {
   878  	buf := &strings.Builder{}
   879  	fmt.Fprintln(buf, "[compiler panic] ", strings.TrimSpace(b.Unwrap().Error()))
   880  	if b.clues.Len() > 0 {
   881  		fmt.Fprintln(buf, "\n"+b.clues.String())
   882  	}
   883  	if len(b.stack) > 0 {
   884  		// Shift stack track by 2 spaces for better readability.
   885  		stack := regexp.MustCompile("(?m)^").ReplaceAll(b.stack, []byte("  "))
   886  		fmt.Fprintln(buf, "\nOriginal stack trace:\n", string(stack))
   887  	}
   888  	return buf.String()
   889  }
   890  
   891  func bailout(cause interface{}) *FatalError {
   892  	b := &FatalError{
   893  		cause: cause,
   894  		stack: debug.Stack(),
   895  	}
   896  	return b
   897  }
   898  
   899  func bailingOut(err interface{}) (*FatalError, bool) {
   900  	fe, ok := err.(*FatalError)
   901  	return fe, ok
   902  }