]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.typeparams] merge master (2f0da6d) into dev.typeparams
authorRob Findley <rfindley@google.com>
Wed, 17 Feb 2021 21:34:00 +0000 (16:34 -0500)
committerRob Findley <rfindley@google.com>
Thu, 18 Feb 2021 01:04:03 +0000 (20:04 -0500)
This was a mostly clean merge, with the exception of codereview.cfg and
changes in src/go/types.

codereview.cfg for dev.typeparams is preserved in this CL. It should be
deleted before merging back to master.

The go/types changes were merged manually. For the most part this
involved taking the union of patches, with the following exceptions:
 + declInfo.aliasPos is removed, as it is not necessary in
   dev.typeparams where we have access to the full TypeSpec.
 + Checker.overflow is updated to use the asBasic converter.
 + A TODO is added to errorcodes.go to ensure that go1.16 error codes
   are preserved.

Change-Id: If9595196852e2163e27a9478df1e7b2c3704947d

17 files changed:
1  2 
src/cmd/dist/test.go
src/go/types/api.go
src/go/types/assignments.go
src/go/types/builtins.go
src/go/types/check.go
src/go/types/check_test.go
src/go/types/decl.go
src/go/types/errorcodes.go
src/go/types/eval_test.go
src/go/types/expr.go
src/go/types/predicates.go
src/go/types/resolver.go
src/go/types/testdata/builtins.src
src/go/types/testdata/stmt0.src
src/go/types/type.go
src/go/types/typexpr.go
test/run.go

Simple merge
Simple merge
Simple merge
Simple merge
index d1672837b8d9d9b678edf595662eb407bd707070,3bc8ee067c2b1b516e98bbcfbc447b91c712fc9c..57c6a2e7b89ff059a31acccf2fc64dee5540ec6c
@@@ -78,12 -85,11 +85,13 @@@ type Checker struct 
        fset *token.FileSet
        pkg  *Package
        *Info
-       nextId uint64                     // unique Id for type parameters (first valid Id is 1)
-       objMap map[Object]*declInfo       // maps package-level objects and (non-interface) methods to declaration info
-       impMap map[importKey]*Package     // maps (import path, source directory) to (complete or fake) package
-       posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
-       typMap map[string]*Named          // maps an instantiated named type hash to a *Named type
-       pkgCnt map[string]int             // counts number of imported packages with a given name (for better error messages)
+       version version                    // accepted language version
++      nextId  uint64                     // unique Id for type parameters (first valid Id is 1)
+       objMap  map[Object]*declInfo       // maps package-level objects and (non-interface) methods to declaration info
+       impMap  map[importKey]*Package     // maps (import path, source directory) to (complete or fake) package
+       posMap  map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
++      typMap  map[string]*Named          // maps an instantiated named type hash to a *Named type
+       pkgCnt  map[string]int             // counts number of imported packages with a given name (for better error messages)
  
        // information collected during type-checking of a set of package files
        // (initialized by Files, valid only for the duration of check.Files;
@@@ -187,17 -178,21 +180,23 @@@ func NewChecker(conf *Config, fset *tok
                info = new(Info)
        }
  
+       version, err := parseGoVersion(conf.GoVersion)
+       if err != nil {
+               panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
+       }
        return &Checker{
-               conf:   conf,
-               fset:   fset,
-               pkg:    pkg,
-               Info:   info,
-               nextId: 1,
-               objMap: make(map[Object]*declInfo),
-               impMap: make(map[importKey]*Package),
-               posMap: make(map[*Interface][]token.Pos),
-               typMap: make(map[string]*Named),
-               pkgCnt: make(map[string]int),
+               conf:    conf,
+               fset:    fset,
+               pkg:     pkg,
+               Info:    info,
+               version: version,
++              nextId:  1,
+               objMap:  make(map[Object]*declInfo),
+               impMap:  make(map[importKey]*Package),
+               posMap:  make(map[*Interface][]token.Pos),
++              typMap:  make(map[string]*Named),
+               pkgCnt:  make(map[string]int),
        }
  }
  
@@@ -279,11 -278,10 +282,14 @@@ func (check *Checker) checkFiles(files 
  
        check.recordUntyped()
  
 +      if check.Info != nil {
 +              sanitizeInfo(check.Info)
 +      }
 +
        check.pkg.complete = true
+       // TODO(rFindley) There's more memory we should release at this point.
        return
  }
  
index 51eae052f375c564a273324bdf61fa7079f38801,ca7d926ca903990bf941af6371a7a6d86cd9b2d8..c92855b3d8004c4ca6e95679e9656ca753704649
@@@ -68,11 -69,11 +69,11 @@@ func splitError(err error) (pos, msg st
        return
  }
  
- func parseFiles(t *testing.T, filenames []string, mode parser.Mode) ([]*ast.File, []error) {
 -func parseFiles(t *testing.T, filenames []string, srcs [][]byte) ([]*ast.File, []error) {
++func parseFiles(t *testing.T, filenames []string, srcs [][]byte, mode parser.Mode) ([]*ast.File, []error) {
        var files []*ast.File
        var errlist []error
-       for _, filename := range filenames {
-               file, err := parser.ParseFile(fset, filename, nil, mode)
+       for i, filename := range filenames {
 -              file, err := parser.ParseFile(fset, filename, srcs[i], parser.AllErrors)
++              file, err := parser.ParseFile(fset, filename, srcs[i], mode)
                if file == nil {
                        t.Fatalf("%s: %s", filename, err)
                }
@@@ -195,13 -208,8 +208,13 @@@ func checkFiles(t *testing.T, goVersio
                t.Fatal("no source files")
        }
  
-       if strings.HasSuffix(sources[0], ".go2") {
 +      mode := parser.AllErrors
++      if strings.HasSuffix(filenames[0], ".go2") {
 +              mode |= parser.ParseTypeParams
 +      }
 +
        // parse files and collect parser errors
-       files, errlist := parseFiles(t, sources, mode)
 -      files, errlist := parseFiles(t, filenames, srcs)
++      files, errlist := parseFiles(t, filenames, srcs, mode)
  
        pkgName := "<no package>"
        if len(files) > 0 {
index f2e68bbd5c3fe38e08f978b4e7f74b9bd50e6573,6462edbd75e3e63eac8f25653eb1e3fdaea4b2ad..c97b1a66bb975aed9b2068fe3f1e9f7e5dad6bbf
@@@ -511,23 -504,9 +511,9 @@@ func (check *Checker) constDecl(obj *Co
  func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) {
        assert(obj.typ == nil)
  
-       // If we have undefined variable types due to errors,
-       // mark variables as used to avoid follow-on errors.
-       // Matches compiler behavior.
-       defer func() {
-               if obj.typ == Typ[Invalid] {
-                       obj.used = true
-               }
-               for _, lhs := range lhs {
-                       if lhs.typ == Typ[Invalid] {
-                               lhs.used = true
-                       }
-               }
-       }()
        // determine type, if any
        if typ != nil {
 -              obj.typ = check.typ(typ)
 +              obj.typ = check.varType(typ)
                // We cannot spread the type to all lhs variables if there
                // are more than one since that would mark them as checked
                // (see Checker.objDecl) and the assignment of init exprs,
@@@ -658,24 -633,17 +644,27 @@@ func (check *Checker) typeDecl(obj *Typ
                check.validType(obj.typ, nil)
        })
  
 -      if aliasPos.IsValid() {
 +      alias := tdecl.Assign.IsValid()
 +      if alias && tdecl.TParams != nil {
 +              // The parser will ensure this but we may still get an invalid AST.
 +              // Complain and continue as regular type definition.
 +              check.error(atPos(tdecl.Assign), 0, "generic type cannot be alias")
 +              alias = false
 +      }
 +
 +      if alias {
 +              // type alias declaration
+               if !check.allowVersion(obj.pkg, 1, 9) {
 -                      check.errorf(atPos(aliasPos), _BadDecl, "type aliases requires go1.9 or later")
++                      check.errorf(atPos(tdecl.Assign), _BadDecl, "type aliases requires go1.9 or later")
+               }
  
                obj.typ = Typ[Invalid]
 -              obj.typ = check.typ(typ)
 +              obj.typ = check.anyType(tdecl.Type)
  
        } else {
 +              // defined type declaration
  
 -              named := &Named{obj: obj}
 +              named := &Named{check: check, obj: obj}
                def.setUnderlying(named)
                obj.typ = named // make sure recursive type declarations terminate
  
index 2837019bf5ec39dd56b77a23c634e7aadec4353f,ac28c3bd13443d65e3db53b41086a92e5751f315..1e39aed07d619cc59b5a8dcbe5d6022f95a515de
@@@ -6,6 -6,6 +6,9 @@@ package type
  
  type errorCode int
  
++// TODO(rFindley): ensure that existing error codes do not change in the
++//                 dev.typeparams branch.
++
  // This file defines the error codes that can be produced during type-checking.
  // Collectively, these codes provide an identifier that may be used to
  // implement special handling for certain types of errors.
@@@ -1315,7 -1367,6 +1318,10 @@@ const 
        //  }
        _InvalidGo
  
+       // _BadDecl occurs when a declaration has invalid syntax.
+       _BadDecl
++
 +      // _Todo is a placeholder for error codes that have not been decided.
 +      // TODO(rFindley) remove this error code after deciding on errors for generics code.
 +      _Todo
  )
Simple merge
index 0d9540245502a26b80798eb28787aa631748d250,aec3172327877b0e6905bc7a1d462b5907229b0c..45cf8c6b417ce082662d7365d68755cfb600296f
@@@ -84,9 -78,73 +84,73 @@@ func (check *Checker) op(m opPredicates
        return true
  }
  
 -      if typ, ok := x.typ.Underlying().(*Basic); ok && isTyped(typ) {
+ // overflow checks that the constant x is representable by its type.
+ // For untyped constants, it checks that the value doesn't become
+ // arbitrarily large.
+ func (check *Checker) overflow(x *operand, op token.Token, opPos token.Pos) {
+       assert(x.mode == constant_)
+       if x.val.Kind() == constant.Unknown {
+               // TODO(gri) We should report exactly what went wrong. At the
+               //           moment we don't have the (go/constant) API for that.
+               //           See also TODO in go/constant/value.go.
+               check.errorf(atPos(opPos), _InvalidConstVal, "constant result is not representable")
+               return
+       }
+       // Typed constants must be representable in
+       // their type after each constant operation.
++      if typ := asBasic(x.typ); typ != nil && isTyped(typ) {
+               check.representable(x, typ)
+               return
+       }
+       // Untyped integer values must not grow arbitrarily.
+       const prec = 512 // 512 is the constant precision
+       if x.val.Kind() == constant.Int && constant.BitLen(x.val) > prec {
+               check.errorf(atPos(opPos), _InvalidConstVal, "constant %s overflow", opName(x.expr))
+               x.val = constant.MakeUnknown()
+       }
+ }
+ // opName returns the name of an operation, or the empty string.
+ // For now, only operations that might overflow are handled.
+ // TODO(gri) Expand this to a general mechanism giving names to
+ //           nodes?
+ func opName(e ast.Expr) string {
+       switch e := e.(type) {
+       case *ast.BinaryExpr:
+               if int(e.Op) < len(op2str2) {
+                       return op2str2[e.Op]
+               }
+       case *ast.UnaryExpr:
+               if int(e.Op) < len(op2str1) {
+                       return op2str1[e.Op]
+               }
+       }
+       return ""
+ }
+ var op2str1 = [...]string{
+       token.XOR: "bitwise complement",
+ }
+ // This is only used for operations that may cause overflow.
+ var op2str2 = [...]string{
+       token.ADD: "addition",
+       token.SUB: "subtraction",
+       token.XOR: "bitwise XOR",
+       token.MUL: "multiplication",
+       token.SHL: "shift",
+ }
  // The unary expression e may be nil. It's passed in for better error messages only.
- func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) {
-       switch op {
+ func (check *Checker) unary(x *operand, e *ast.UnaryExpr) {
+       check.expr(x, e.X)
+       if x.mode == invalid {
+               return
+       }
+       switch e.Op {
        case token.AND:
                // spec: "As an exception to the addressability
                // requirement x may also be a composite literal."
        }
  
        if x.mode == constant_ {
+               if x.val.Kind() == constant.Unknown {
+                       // nothing to do (and don't cause an error below in the overflow check)
+                       return
+               }
 +              typ := asBasic(x.typ)
                var prec uint
 -              if isUnsigned(x.typ) {
 -                      prec = uint(check.conf.sizeof(x.typ) * 8)
 +              if isUnsigned(typ) {
 +                      prec = uint(check.conf.sizeof(typ) * 8)
                }
-               x.val = constant.UnaryOp(op, x.val, prec)
-               // Typed constants must be representable in
-               // their type after each constant operation.
-               if isTyped(typ) {
-                       if e != nil {
-                               x.expr = e // for better error message
-                       }
-                       check.representable(x, typ)
-               }
+               x.val = constant.UnaryOp(e.Op, x.val, prec)
+               x.expr = e
+               check.overflow(x, e.Op, x.Pos())
                return
        }
  
@@@ -806,29 -870,25 +890,30 @@@ func (check *Checker) shift(x, y *opera
        x.mode = value
  }
  
 -var binaryOpPredicates = opPredicates{
 -      token.ADD: func(typ Type) bool { return isNumeric(typ) || isString(typ) },
 -      token.SUB: isNumeric,
 -      token.MUL: isNumeric,
 -      token.QUO: isNumeric,
 -      token.REM: isInteger,
 +var binaryOpPredicates opPredicates
 +
 +func init() {
 +      // Setting binaryOpPredicates in init avoids declaration cycles.
 +      binaryOpPredicates = opPredicates{
 +              token.ADD: isNumericOrString,
 +              token.SUB: isNumeric,
 +              token.MUL: isNumeric,
 +              token.QUO: isNumeric,
 +              token.REM: isInteger,
  
 -      token.AND:     isInteger,
 -      token.OR:      isInteger,
 -      token.XOR:     isInteger,
 -      token.AND_NOT: isInteger,
 +              token.AND:     isInteger,
 +              token.OR:      isInteger,
 +              token.XOR:     isInteger,
 +              token.AND_NOT: isInteger,
  
 -      token.LAND: isBoolean,
 -      token.LOR:  isBoolean,
 +              token.LAND: isBoolean,
 +              token.LOR:  isBoolean,
 +      }
  }
  
- // The binary expression e may be nil. It's passed in for better error messages only.
- func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) {
+ // If e != nil, it must be the binary expression; it may be nil for non-constant expressions
+ // (when invoked for an assignment operation where the binary expression is implicit).
+ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) {
        var y operand
  
        check.expr(x, lhs)
        }
  
        if x.mode == constant_ && y.mode == constant_ {
-               xval := x.val
-               yval := y.val
+               // if either x or y has an unknown value, the result is unknown
+               if x.val.Kind() == constant.Unknown || y.val.Kind() == constant.Unknown {
+                       x.val = constant.MakeUnknown()
+                       // x.typ is unchanged
+                       return
+               }
 +              typ := asBasic(x.typ)
                // force integer division of integer operands
 -              if op == token.QUO && isInteger(x.typ) {
 +              if op == token.QUO && isInteger(typ) {
                        op = token.QUO_ASSIGN
                }
-               x.val = constant.BinaryOp(xval, op, yval)
-               // report error if valid operands lead to an invalid result
-               if xval.Kind() != constant.Unknown && yval.Kind() != constant.Unknown && x.val.Kind() == constant.Unknown {
-                       // TODO(gri) We should report exactly what went wrong. At the
-                       //           moment we don't have the (go/constant) API for that.
-                       //           See also TODO in go/constant/value.go.
-                       check.errorf(atPos(opPos), _InvalidConstVal, "constant result is not representable")
-                       // TODO(gri) Should we mark operands with unknown values as invalid?
-               }
-               // Typed constants must be representable in
-               // their type after each constant operation.
-               if isTyped(typ) {
-                       if e != nil {
-                               x.expr = e // for better error message
-                       }
-                       check.representable(x, typ)
-               }
+               x.val = constant.BinaryOp(x.val, op, y.val)
+               x.expr = e
+               check.overflow(x, op, opPos)
                return
        }
  
index 023327496713611ecdf27ea3cffbae36f80caf4f,954a7ca987a8f554c79264dae3b6da8905405db2..7a99c1ff99750b5288ea3cb1cb9752b41a50eca2
@@@ -6,76 -6,68 +6,75 @@@
  
  package types
  
-       "sort"
 +import (
 +      "go/token"
 +)
 +
 +// isNamed reports whether typ has a name.
 +// isNamed may be called with types that are not fully set up.
  func isNamed(typ Type) bool {
 -      if _, ok := typ.(*Basic); ok {
 -              return ok
 +      switch typ.(type) {
 +      case *Basic, *Named, *TypeParam, *instance:
 +              return true
        }
 -      _, ok := typ.(*Named)
 -      return ok
 -}
 -
 -func isBoolean(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsBoolean != 0
 -}
 -
 -func isInteger(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsInteger != 0
 -}
 -
 -func isUnsigned(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsUnsigned != 0
 -}
 -
 -func isFloat(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsFloat != 0
 -}
 -
 -func isComplex(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsComplex != 0
 +      return false
  }
  
 -func isNumeric(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsNumeric != 0
 +// isGeneric reports whether a type is a generic, uninstantiated type (generic
 +// signatures are not included).
 +func isGeneric(typ Type) bool {
 +      // A parameterized type is only instantiated if it doesn't have an instantiation already.
 +      named, _ := typ.(*Named)
 +      return named != nil && named.obj != nil && named.tparams != nil && named.targs == nil
  }
  
 -func isString(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsString != 0
 +func is(typ Type, what BasicInfo) bool {
 +      switch t := optype(typ).(type) {
 +      case *Basic:
 +              return t.info&what != 0
 +      case *Sum:
 +              return t.is(func(typ Type) bool { return is(typ, what) })
 +      }
 +      return false
  }
  
 +func isBoolean(typ Type) bool  { return is(typ, IsBoolean) }
 +func isInteger(typ Type) bool  { return is(typ, IsInteger) }
 +func isUnsigned(typ Type) bool { return is(typ, IsUnsigned) }
 +func isFloat(typ Type) bool    { return is(typ, IsFloat) }
 +func isComplex(typ Type) bool  { return is(typ, IsComplex) }
 +func isNumeric(typ Type) bool  { return is(typ, IsNumeric) }
 +func isString(typ Type) bool   { return is(typ, IsString) }
 +
 +// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not
 +// produce the expected result because a type list that contains both an integer
 +// and a floating-point type is neither (all) integers, nor (all) floats.
 +// Use isIntegerOrFloat instead.
 +func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) }
 +
 +// isNumericOrString is the equivalent of isIntegerOrFloat for isNumeric(typ) || isString(typ).
 +func isNumericOrString(typ Type) bool { return is(typ, IsNumeric|IsString) }
 +
 +// isTyped reports whether typ is typed; i.e., not an untyped
 +// constant or boolean. isTyped may be called with types that
 +// are not fully set up.
  func isTyped(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return !ok || t.info&IsUntyped == 0
 +      // isTyped is called with types that are not fully
 +      // set up. Must not call asBasic()!
 +      // A *Named or *instance type is always typed, so
 +      // we only need to check if we have a true *Basic
 +      // type.
 +      t, _ := typ.(*Basic)
 +      return t == nil || t.info&IsUntyped == 0
  }
  
 +// isUntyped(typ) is the same as !isTyped(typ).
  func isUntyped(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsUntyped != 0
 -}
 -
 -func isOrdered(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsOrdered != 0
 +      return !isTyped(typ)
  }
  
 -func isConstType(typ Type) bool {
 -      t, ok := typ.Underlying().(*Basic)
 -      return ok && t.info&IsConstType != 0
 -}
 +func isOrdered(typ Type) bool   { return is(typ, IsOrdered) }
 +func isConstType(typ Type) bool { return is(typ, IsConstType) }
  
  // IsInterface reports whether typ is an interface type.
  func IsInterface(typ Type) bool {
Simple merge
Simple merge
Simple merge
index 0fcefefb7348b0018c0e7be05281b2b1f1e7817f,66e194e967920ecb66ec4bc0a16f1c7ef2711d57..21892c9270d0dab06bf7652e0224baa7692bac82
@@@ -4,12 -4,6 +4,11 @@@
  
  package types
  
-       "sort"
 +import (
 +      "fmt"
 +      "go/token"
 +)
 +
  // A Type represents a type of Go.
  // All types implement the Type interface.
  type Type interface {
@@@ -591,10 -394,9 +590,10 @@@ func (t *Interface) Complete() *Interfa
        }
  
        if methods != nil {
-               sort.Sort(byUniqueMethodName(methods))
+               sortMethods(methods)
                t.allMethods = methods
        }
 +      t.allTypes = allTypes
  
        return t
  }
index bca0a6664f3ca9c012f11af8c223a933387bc111,b9249494fa16d0159d65f7e1c8213d8c3e5ae8a7..503f9c71aca63501fc2798e5219aa4d2a6b78eec
@@@ -832,13 -518,13 +832,13 @@@ func (check *Checker) interfaceType(ity
        }
  
        // sort for API stability
-       sort.Sort(byUniqueMethodName(ityp.methods))
-       sort.Stable(byUniqueTypeName(ityp.embeddeds))
+       sortMethods(ityp.methods)
+       sortTypes(ityp.embeddeds)
  
 -      check.later(func() { check.completeInterface(ityp) })
 +      check.later(func() { check.completeInterface(iface.Pos(), ityp) })
  }
  
 -func (check *Checker) completeInterface(ityp *Interface) {
 +func (check *Checker) completeInterface(pos token.Pos, ityp *Interface) {
        if ityp.allMethods != nil {
                return
        }
                sort.Sort(byUniqueMethodName(methods))
                ityp.allMethods = methods
        }
 +      ityp.allTypes = allTypes
 +}
 +
 +// intersect computes the intersection of the types x and y.
 +// Note: A incomming nil type stands for the top type. A top
 +// type result is returned as nil.
 +func intersect(x, y Type) (r Type) {
 +      defer func() {
 +              if r == theTop {
 +                      r = nil
 +              }
 +      }()
 +
 +      switch {
 +      case x == theBottom || y == theBottom:
 +              return theBottom
 +      case x == nil || x == theTop:
 +              return y
 +      case y == nil || x == theTop:
 +              return x
 +      }
 +
 +      xtypes := unpackType(x)
 +      ytypes := unpackType(y)
 +      // Compute the list rtypes which includes only
 +      // types that are in both xtypes and ytypes.
 +      // Quadratic algorithm, but good enough for now.
 +      // TODO(gri) fix this
 +      var rtypes []Type
 +      for _, x := range xtypes {
 +              if includes(ytypes, x) {
 +                      rtypes = append(rtypes, x)
 +              }
 +      }
 +
 +      if rtypes == nil {
 +              return theBottom
 +      }
 +      return NewSum(rtypes)
  }
  
+ func sortTypes(list []Type) {
+       sort.Stable(byUniqueTypeName(list))
+ }
  // byUniqueTypeName named type lists can be sorted by their unique type names.
  type byUniqueTypeName []Type
  
diff --cc test/run.go
Simple merge