github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/src/cmd/go/main.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bufio" 9 "bytes" 10 "flag" 11 "fmt" 12 "go/build" 13 "io" 14 "log" 15 "os" 16 "os/exec" 17 "path" 18 "path/filepath" 19 "regexp" 20 "runtime" 21 "strings" 22 "sync" 23 "text/template" 24 "unicode" 25 "unicode/utf8" 26 ) 27 28 // A Command is an implementation of a go command 29 // like go build or go fix. 30 type Command struct { 31 // Run runs the command. 32 // The args are the arguments after the command name. 33 Run func(cmd *Command, args []string) 34 35 // UsageLine is the one-line usage message. 36 // The first word in the line is taken to be the command name. 37 UsageLine string 38 39 // Short is the short description shown in the 'go help' output. 40 Short string 41 42 // Long is the long message shown in the 'go help <this-command>' output. 43 Long string 44 45 // Flag is a set of flags specific to this command. 46 Flag flag.FlagSet 47 48 // CustomFlags indicates that the command will do its own 49 // flag parsing. 50 CustomFlags bool 51 } 52 53 // Name returns the command's name: the first word in the usage line. 54 func (c *Command) Name() string { 55 name := c.UsageLine 56 i := strings.Index(name, " ") 57 if i >= 0 { 58 name = name[:i] 59 } 60 return name 61 } 62 63 func (c *Command) Usage() { 64 fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) 65 fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) 66 os.Exit(2) 67 } 68 69 // Runnable reports whether the command can be run; otherwise 70 // it is a documentation pseudo-command such as importpath. 71 func (c *Command) Runnable() bool { 72 return c.Run != nil 73 } 74 75 // Commands lists the available commands and help topics. 76 // The order here is the order in which they are printed by 'go help'. 77 var commands = []*Command{ 78 cmdBuild, 79 cmdClean, 80 cmdDoc, 81 cmdEnv, 82 cmdBug, 83 cmdFix, 84 cmdFmt, 85 cmdGenerate, 86 cmdGet, 87 cmdInstall, 88 cmdList, 89 cmdRun, 90 cmdTest, 91 cmdTool, 92 cmdVersion, 93 cmdVet, 94 95 helpC, 96 helpBuildmode, 97 helpFileType, 98 helpGopath, 99 helpEnvironment, 100 helpImportPath, 101 helpPackages, 102 helpTestflag, 103 helpTestfunc, 104 } 105 106 var exitStatus = 0 107 var exitMu sync.Mutex 108 109 func setExitStatus(n int) { 110 exitMu.Lock() 111 if exitStatus < n { 112 exitStatus = n 113 } 114 exitMu.Unlock() 115 } 116 117 var origEnv []string 118 var newEnv []envVar 119 120 func main() { 121 _ = go11tag 122 flag.Usage = usage 123 flag.Parse() 124 log.SetFlags(0) 125 126 args := flag.Args() 127 if len(args) < 1 { 128 usage() 129 } 130 131 if args[0] == "help" { 132 help(args[1:]) 133 return 134 } 135 136 // Diagnose common mistake: GOPATH==GOROOT. 137 // This setting is equivalent to not setting GOPATH at all, 138 // which is not what most people want when they do it. 139 if gopath := buildContext.GOPATH; gopath == runtime.GOROOT() { 140 fmt.Fprintf(os.Stderr, "warning: GOPATH set to GOROOT (%s) has no effect\n", gopath) 141 } else { 142 for _, p := range filepath.SplitList(gopath) { 143 // Note: using HasPrefix instead of Contains because a ~ can appear 144 // in the middle of directory elements, such as /tmp/git-1.8.2~rc3 145 // or C:\PROGRA~1. Only ~ as a path prefix has meaning to the shell. 146 if strings.HasPrefix(p, "~") { 147 fmt.Fprintf(os.Stderr, "go: GOPATH entry cannot start with shell metacharacter '~': %q\n", p) 148 os.Exit(2) 149 } 150 if !filepath.IsAbs(p) { 151 fmt.Fprintf(os.Stderr, "go: GOPATH entry is relative; must be absolute path: %q.\nFor more details see: 'go help gopath'\n", p) 152 os.Exit(2) 153 } 154 } 155 } 156 157 if fi, err := os.Stat(goroot); err != nil || !fi.IsDir() { 158 fmt.Fprintf(os.Stderr, "go: cannot find GOROOT directory: %v\n", goroot) 159 os.Exit(2) 160 } 161 162 // Set environment (GOOS, GOARCH, etc) explicitly. 163 // In theory all the commands we invoke should have 164 // the same default computation of these as we do, 165 // but in practice there might be skew 166 // This makes sure we all agree. 167 origEnv = os.Environ() 168 newEnv = mkEnv() 169 for _, env := range newEnv { 170 if os.Getenv(env.name) != env.value { 171 os.Setenv(env.name, env.value) 172 } 173 } 174 175 for _, cmd := range commands { 176 if cmd.Name() == args[0] && cmd.Runnable() { 177 cmd.Flag.Usage = func() { cmd.Usage() } 178 if cmd.CustomFlags { 179 args = args[1:] 180 } else { 181 cmd.Flag.Parse(args[1:]) 182 args = cmd.Flag.Args() 183 } 184 cmd.Run(cmd, args) 185 exit() 186 return 187 } 188 } 189 190 fmt.Fprintf(os.Stderr, "go: unknown subcommand %q\nRun 'go help' for usage.\n", args[0]) 191 setExitStatus(2) 192 exit() 193 } 194 195 var usageTemplate = `Go is a tool for managing Go source code. 196 197 Usage: 198 199 go command [arguments] 200 201 The commands are: 202 {{range .}}{{if .Runnable}} 203 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 204 205 Use "go help [command]" for more information about a command. 206 207 Additional help topics: 208 {{range .}}{{if not .Runnable}} 209 {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 210 211 Use "go help [topic]" for more information about that topic. 212 213 ` 214 215 var helpTemplate = `{{if .Runnable}}usage: go {{.UsageLine}} 216 217 {{end}}{{.Long | trim}} 218 ` 219 220 var documentationTemplate = `{{range .}}{{if .Short}}{{.Short | capitalize}} 221 222 {{end}}{{if .Runnable}}Usage: 223 224 go {{.UsageLine}} 225 226 {{end}}{{.Long | trim}} 227 228 229 {{end}}` 230 231 // commentWriter writes a Go comment to the underlying io.Writer, 232 // using line comment form (//). 233 type commentWriter struct { 234 W io.Writer 235 wroteSlashes bool // Wrote "//" at the beginning of the current line. 236 } 237 238 func (c *commentWriter) Write(p []byte) (int, error) { 239 var n int 240 for i, b := range p { 241 if !c.wroteSlashes { 242 s := "//" 243 if b != '\n' { 244 s = "// " 245 } 246 if _, err := io.WriteString(c.W, s); err != nil { 247 return n, err 248 } 249 c.wroteSlashes = true 250 } 251 n0, err := c.W.Write(p[i : i+1]) 252 n += n0 253 if err != nil { 254 return n, err 255 } 256 if b == '\n' { 257 c.wroteSlashes = false 258 } 259 } 260 return len(p), nil 261 } 262 263 // An errWriter wraps a writer, recording whether a write error occurred. 264 type errWriter struct { 265 w io.Writer 266 err error 267 } 268 269 func (w *errWriter) Write(b []byte) (int, error) { 270 n, err := w.w.Write(b) 271 if err != nil { 272 w.err = err 273 } 274 return n, err 275 } 276 277 // tmpl executes the given template text on data, writing the result to w. 278 func tmpl(w io.Writer, text string, data interface{}) { 279 t := template.New("top") 280 t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) 281 template.Must(t.Parse(text)) 282 ew := &errWriter{w: w} 283 err := t.Execute(ew, data) 284 if ew.err != nil { 285 // I/O error writing. Ignore write on closed pipe. 286 if strings.Contains(ew.err.Error(), "pipe") { 287 os.Exit(1) 288 } 289 fatalf("writing output: %v", ew.err) 290 } 291 if err != nil { 292 panic(err) 293 } 294 } 295 296 func capitalize(s string) string { 297 if s == "" { 298 return s 299 } 300 r, n := utf8.DecodeRuneInString(s) 301 return string(unicode.ToTitle(r)) + s[n:] 302 } 303 304 func printUsage(w io.Writer) { 305 bw := bufio.NewWriter(w) 306 tmpl(bw, usageTemplate, commands) 307 bw.Flush() 308 } 309 310 func usage() { 311 // special case "go test -h" 312 if len(os.Args) > 1 && os.Args[1] == "test" { 313 os.Stderr.WriteString(testUsage + "\n\n" + 314 strings.TrimSpace(testFlag1) + "\n\n\t" + 315 strings.TrimSpace(testFlag2) + "\n") 316 os.Exit(2) 317 } 318 printUsage(os.Stderr) 319 os.Exit(2) 320 } 321 322 // help implements the 'help' command. 323 func help(args []string) { 324 if len(args) == 0 { 325 printUsage(os.Stdout) 326 // not exit 2: succeeded at 'go help'. 327 return 328 } 329 if len(args) != 1 { 330 fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n") 331 os.Exit(2) // failed at 'go help' 332 } 333 334 arg := args[0] 335 336 // 'go help documentation' generates doc.go. 337 if arg == "documentation" { 338 fmt.Println("// Copyright 2011 The Go Authors. All rights reserved.") 339 fmt.Println("// Use of this source code is governed by a BSD-style") 340 fmt.Println("// license that can be found in the LICENSE file.") 341 fmt.Println() 342 fmt.Println("// DO NOT EDIT THIS FILE. GENERATED BY mkalldocs.sh.") 343 fmt.Println("// Edit the documentation in other files and rerun mkalldocs.sh to generate this one.") 344 fmt.Println() 345 buf := new(bytes.Buffer) 346 printUsage(buf) 347 usage := &Command{Long: buf.String()} 348 tmpl(&commentWriter{W: os.Stdout}, documentationTemplate, append([]*Command{usage}, commands...)) 349 fmt.Println("package main") 350 return 351 } 352 353 for _, cmd := range commands { 354 if cmd.Name() == arg { 355 tmpl(os.Stdout, helpTemplate, cmd) 356 // not exit 2: succeeded at 'go help cmd'. 357 return 358 } 359 } 360 361 fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'go help'.\n", arg) 362 os.Exit(2) // failed at 'go help cmd' 363 } 364 365 // importPathsNoDotExpansion returns the import paths to use for the given 366 // command line, but it does no ... expansion. 367 func importPathsNoDotExpansion(args []string) []string { 368 if len(args) == 0 { 369 return []string{"."} 370 } 371 var out []string 372 for _, a := range args { 373 // Arguments are supposed to be import paths, but 374 // as a courtesy to Windows developers, rewrite \ to / 375 // in command-line arguments. Handles .\... and so on. 376 if filepath.Separator == '\\' { 377 a = strings.Replace(a, `\`, `/`, -1) 378 } 379 380 // Put argument in canonical form, but preserve leading ./. 381 if strings.HasPrefix(a, "./") { 382 a = "./" + path.Clean(a) 383 if a == "./." { 384 a = "." 385 } 386 } else { 387 a = path.Clean(a) 388 } 389 if isMetaPackage(a) { 390 out = append(out, allPackages(a)...) 391 continue 392 } 393 out = append(out, a) 394 } 395 return out 396 } 397 398 // importPaths returns the import paths to use for the given command line. 399 func importPaths(args []string) []string { 400 args = importPathsNoDotExpansion(args) 401 var out []string 402 for _, a := range args { 403 if strings.Contains(a, "...") { 404 if build.IsLocalImport(a) { 405 out = append(out, allPackagesInFS(a)...) 406 } else { 407 out = append(out, allPackages(a)...) 408 } 409 continue 410 } 411 out = append(out, a) 412 } 413 return out 414 } 415 416 var atexitFuncs []func() 417 418 func atexit(f func()) { 419 atexitFuncs = append(atexitFuncs, f) 420 } 421 422 func exit() { 423 for _, f := range atexitFuncs { 424 f() 425 } 426 os.Exit(exitStatus) 427 } 428 429 func fatalf(format string, args ...interface{}) { 430 errorf(format, args...) 431 exit() 432 } 433 434 func errorf(format string, args ...interface{}) { 435 log.Printf(format, args...) 436 setExitStatus(1) 437 } 438 439 func exitIfErrors() { 440 if exitStatus != 0 { 441 exit() 442 } 443 } 444 445 func run(cmdargs ...interface{}) { 446 cmdline := stringList(cmdargs...) 447 if buildN || buildX { 448 fmt.Printf("%s\n", strings.Join(cmdline, " ")) 449 if buildN { 450 return 451 } 452 } 453 454 cmd := exec.Command(cmdline[0], cmdline[1:]...) 455 cmd.Stdout = os.Stdout 456 cmd.Stderr = os.Stderr 457 if err := cmd.Run(); err != nil { 458 errorf("%v", err) 459 } 460 } 461 462 // envForDir returns a copy of the environment 463 // suitable for running in the given directory. 464 // The environment is the current process's environment 465 // but with an updated $PWD, so that an os.Getwd in the 466 // child will be faster. 467 func envForDir(dir string, base []string) []string { 468 // Internally we only use rooted paths, so dir is rooted. 469 // Even if dir is not rooted, no harm done. 470 return mergeEnvLists([]string{"PWD=" + dir}, base) 471 } 472 473 // mergeEnvLists merges the two environment lists such that 474 // variables with the same name in "in" replace those in "out". 475 // This always returns a newly allocated slice. 476 func mergeEnvLists(in, out []string) []string { 477 out = append([]string(nil), out...) 478 NextVar: 479 for _, inkv := range in { 480 k := strings.SplitAfterN(inkv, "=", 2)[0] 481 for i, outkv := range out { 482 if strings.HasPrefix(outkv, k) { 483 out[i] = inkv 484 continue NextVar 485 } 486 } 487 out = append(out, inkv) 488 } 489 return out 490 } 491 492 // matchPattern(pattern)(name) reports whether 493 // name matches pattern. Pattern is a limited glob 494 // pattern in which '...' means 'any string' and there 495 // is no other special syntax. 496 func matchPattern(pattern string) func(name string) bool { 497 re := regexp.QuoteMeta(pattern) 498 re = strings.Replace(re, `\.\.\.`, `.*`, -1) 499 // Special case: foo/... matches foo too. 500 if strings.HasSuffix(re, `/.*`) { 501 re = re[:len(re)-len(`/.*`)] + `(/.*)?` 502 } 503 reg := regexp.MustCompile(`^` + re + `$`) 504 return func(name string) bool { 505 return reg.MatchString(name) 506 } 507 } 508 509 // hasPathPrefix reports whether the path s begins with the 510 // elements in prefix. 511 func hasPathPrefix(s, prefix string) bool { 512 switch { 513 default: 514 return false 515 case len(s) == len(prefix): 516 return s == prefix 517 case len(s) > len(prefix): 518 if prefix != "" && prefix[len(prefix)-1] == '/' { 519 return strings.HasPrefix(s, prefix) 520 } 521 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 522 } 523 } 524 525 // hasFilePathPrefix reports whether the filesystem path s begins with the 526 // elements in prefix. 527 func hasFilePathPrefix(s, prefix string) bool { 528 sv := strings.ToUpper(filepath.VolumeName(s)) 529 pv := strings.ToUpper(filepath.VolumeName(prefix)) 530 s = s[len(sv):] 531 prefix = prefix[len(pv):] 532 switch { 533 default: 534 return false 535 case sv != pv: 536 return false 537 case len(s) == len(prefix): 538 return s == prefix 539 case len(s) > len(prefix): 540 if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { 541 return strings.HasPrefix(s, prefix) 542 } 543 return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix 544 } 545 } 546 547 // expandPath returns the symlink-expanded form of path. 548 func expandPath(p string) string { 549 x, err := filepath.EvalSymlinks(p) 550 if err == nil { 551 return x 552 } 553 return p 554 } 555 556 // treeCanMatchPattern(pattern)(name) reports whether 557 // name or children of name can possibly match pattern. 558 // Pattern is the same limited glob accepted by matchPattern. 559 func treeCanMatchPattern(pattern string) func(name string) bool { 560 wildCard := false 561 if i := strings.Index(pattern, "..."); i >= 0 { 562 wildCard = true 563 pattern = pattern[:i] 564 } 565 return func(name string) bool { 566 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || 567 wildCard && strings.HasPrefix(name, pattern) 568 } 569 } 570 571 // allPackages returns all the packages that can be found 572 // under the $GOPATH directories and $GOROOT matching pattern. 573 // The pattern is either "all" (all packages), "std" (standard packages), 574 // "cmd" (standard commands), or a path including "...". 575 func allPackages(pattern string) []string { 576 pkgs := matchPackages(pattern) 577 if len(pkgs) == 0 { 578 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 579 } 580 return pkgs 581 } 582 583 func matchPackages(pattern string) []string { 584 match := func(string) bool { return true } 585 treeCanMatch := func(string) bool { return true } 586 if !isMetaPackage(pattern) { 587 match = matchPattern(pattern) 588 treeCanMatch = treeCanMatchPattern(pattern) 589 } 590 591 have := map[string]bool{ 592 "builtin": true, // ignore pseudo-package that exists only for documentation 593 } 594 if !buildContext.CgoEnabled { 595 have["runtime/cgo"] = true // ignore during walk 596 } 597 var pkgs []string 598 599 for _, src := range buildContext.SrcDirs() { 600 if (pattern == "std" || pattern == "cmd") && src != gorootSrc { 601 continue 602 } 603 src = filepath.Clean(src) + string(filepath.Separator) 604 root := src 605 if pattern == "cmd" { 606 root += "cmd" + string(filepath.Separator) 607 } 608 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 609 if err != nil || !fi.IsDir() || path == src { 610 return nil 611 } 612 613 // Avoid .foo, _foo, and testdata directory trees. 614 _, elem := filepath.Split(path) 615 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { 616 return filepath.SkipDir 617 } 618 619 name := filepath.ToSlash(path[len(src):]) 620 if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { 621 // The name "std" is only the standard library. 622 // If the name is cmd, it's the root of the command tree. 623 return filepath.SkipDir 624 } 625 if !treeCanMatch(name) { 626 return filepath.SkipDir 627 } 628 if have[name] { 629 return nil 630 } 631 have[name] = true 632 if !match(name) { 633 return nil 634 } 635 _, err = buildContext.ImportDir(path, 0) 636 if err != nil { 637 if _, noGo := err.(*build.NoGoError); noGo { 638 return nil 639 } 640 } 641 pkgs = append(pkgs, name) 642 return nil 643 }) 644 } 645 return pkgs 646 } 647 648 // allPackagesInFS is like allPackages but is passed a pattern 649 // beginning ./ or ../, meaning it should scan the tree rooted 650 // at the given directory. There are ... in the pattern too. 651 func allPackagesInFS(pattern string) []string { 652 pkgs := matchPackagesInFS(pattern) 653 if len(pkgs) == 0 { 654 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) 655 } 656 return pkgs 657 } 658 659 func matchPackagesInFS(pattern string) []string { 660 // Find directory to begin the scan. 661 // Could be smarter but this one optimization 662 // is enough for now, since ... is usually at the 663 // end of a path. 664 i := strings.Index(pattern, "...") 665 dir, _ := path.Split(pattern[:i]) 666 667 // pattern begins with ./ or ../. 668 // path.Clean will discard the ./ but not the ../. 669 // We need to preserve the ./ for pattern matching 670 // and in the returned import paths. 671 prefix := "" 672 if strings.HasPrefix(pattern, "./") { 673 prefix = "./" 674 } 675 match := matchPattern(pattern) 676 677 var pkgs []string 678 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 679 if err != nil || !fi.IsDir() { 680 return nil 681 } 682 if path == dir { 683 // filepath.Walk starts at dir and recurses. For the recursive case, 684 // the path is the result of filepath.Join, which calls filepath.Clean. 685 // The initial case is not Cleaned, though, so we do this explicitly. 686 // 687 // This converts a path like "./io/" to "io". Without this step, running 688 // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io 689 // package, because prepending the prefix "./" to the unclean path would 690 // result in "././io", and match("././io") returns false. 691 path = filepath.Clean(path) 692 } 693 694 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". 695 _, elem := filepath.Split(path) 696 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." 697 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { 698 return filepath.SkipDir 699 } 700 701 name := prefix + filepath.ToSlash(path) 702 if !match(name) { 703 return nil 704 } 705 706 // We keep the directory if we can import it, or if we can't import it 707 // due to invalid Go source files. This means that directories containing 708 // parse errors will be built (and fail) instead of being silently skipped 709 // as not matching the pattern. Go 1.5 and earlier skipped, but that 710 // behavior means people miss serious mistakes. 711 // See golang.org/issue/11407. 712 if p, err := buildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { 713 if _, noGo := err.(*build.NoGoError); !noGo { 714 log.Print(err) 715 } 716 return nil 717 } 718 pkgs = append(pkgs, name) 719 return nil 720 }) 721 return pkgs 722 } 723 724 // stringList's arguments should be a sequence of string or []string values. 725 // stringList flattens them into a single []string. 726 func stringList(args ...interface{}) []string { 727 var x []string 728 for _, arg := range args { 729 switch arg := arg.(type) { 730 case []string: 731 x = append(x, arg...) 732 case string: 733 x = append(x, arg) 734 default: 735 panic("stringList: invalid argument of type " + fmt.Sprintf("%T", arg)) 736 } 737 } 738 return x 739 } 740 741 // toFold returns a string with the property that 742 // strings.EqualFold(s, t) iff toFold(s) == toFold(t) 743 // This lets us test a large set of strings for fold-equivalent 744 // duplicates without making a quadratic number of calls 745 // to EqualFold. Note that strings.ToUpper and strings.ToLower 746 // have the desired property in some corner cases. 747 func toFold(s string) string { 748 // Fast path: all ASCII, no upper case. 749 // Most paths look like this already. 750 for i := 0; i < len(s); i++ { 751 c := s[i] 752 if c >= utf8.RuneSelf || 'A' <= c && c <= 'Z' { 753 goto Slow 754 } 755 } 756 return s 757 758 Slow: 759 var buf bytes.Buffer 760 for _, r := range s { 761 // SimpleFold(x) cycles to the next equivalent rune > x 762 // or wraps around to smaller values. Iterate until it wraps, 763 // and we've found the minimum value. 764 for { 765 r0 := r 766 r = unicode.SimpleFold(r0) 767 if r <= r0 { 768 break 769 } 770 } 771 // Exception to allow fast path above: A-Z => a-z 772 if 'A' <= r && r <= 'Z' { 773 r += 'a' - 'A' 774 } 775 buf.WriteRune(r) 776 } 777 return buf.String() 778 } 779 780 // foldDup reports a pair of strings from the list that are 781 // equal according to strings.EqualFold. 782 // It returns "", "" if there are no such strings. 783 func foldDup(list []string) (string, string) { 784 clash := map[string]string{} 785 for _, s := range list { 786 fold := toFold(s) 787 if t := clash[fold]; t != "" { 788 if s > t { 789 s, t = t, s 790 } 791 return s, t 792 } 793 clash[fold] = s 794 } 795 return "", "" 796 }