lint.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. package stylecheck // import "honnef.co/go/tools/stylecheck"
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "go/constant"
  6. "go/token"
  7. "go/types"
  8. "strconv"
  9. "strings"
  10. "unicode"
  11. "unicode/utf8"
  12. "honnef.co/go/tools/config"
  13. "honnef.co/go/tools/internal/passes/buildssa"
  14. . "honnef.co/go/tools/lint/lintdsl"
  15. "honnef.co/go/tools/ssa"
  16. "golang.org/x/tools/go/analysis"
  17. "golang.org/x/tools/go/analysis/passes/inspect"
  18. "golang.org/x/tools/go/ast/inspector"
  19. "golang.org/x/tools/go/types/typeutil"
  20. )
  21. func CheckPackageComment(pass *analysis.Pass) (interface{}, error) {
  22. // - At least one file in a non-main package should have a package comment
  23. //
  24. // - The comment should be of the form
  25. // "Package x ...". This has a slight potential for false
  26. // positives, as multiple files can have package comments, in
  27. // which case they get appended. But that doesn't happen a lot in
  28. // the real world.
  29. if pass.Pkg.Name() == "main" {
  30. return nil, nil
  31. }
  32. hasDocs := false
  33. for _, f := range pass.Files {
  34. if IsInTest(pass, f) {
  35. continue
  36. }
  37. if f.Doc != nil && len(f.Doc.List) > 0 {
  38. hasDocs = true
  39. prefix := "Package " + f.Name.Name + " "
  40. if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
  41. ReportNodef(pass, f.Doc, `package comment should be of the form "%s..."`, prefix)
  42. }
  43. f.Doc.Text()
  44. }
  45. }
  46. if !hasDocs {
  47. for _, f := range pass.Files {
  48. if IsInTest(pass, f) {
  49. continue
  50. }
  51. ReportNodef(pass, f, "at least one file in a package should have a package comment")
  52. }
  53. }
  54. return nil, nil
  55. }
  56. func CheckDotImports(pass *analysis.Pass) (interface{}, error) {
  57. for _, f := range pass.Files {
  58. imports:
  59. for _, imp := range f.Imports {
  60. path := imp.Path.Value
  61. path = path[1 : len(path)-1]
  62. for _, w := range config.For(pass).DotImportWhitelist {
  63. if w == path {
  64. continue imports
  65. }
  66. }
  67. if imp.Name != nil && imp.Name.Name == "." && !IsInTest(pass, f) {
  68. ReportNodefFG(pass, imp, "should not use dot imports")
  69. }
  70. }
  71. }
  72. return nil, nil
  73. }
  74. func CheckBlankImports(pass *analysis.Pass) (interface{}, error) {
  75. fset := pass.Fset
  76. for _, f := range pass.Files {
  77. if IsInMain(pass, f) || IsInTest(pass, f) {
  78. continue
  79. }
  80. // Collect imports of the form `import _ "foo"`, i.e. with no
  81. // parentheses, as their comment will be associated with the
  82. // (paren-free) GenDecl, not the import spec itself.
  83. //
  84. // We don't directly process the GenDecl so that we can
  85. // correctly handle the following:
  86. //
  87. // import _ "foo"
  88. // import _ "bar"
  89. //
  90. // where only the first import should get flagged.
  91. skip := map[ast.Spec]bool{}
  92. ast.Inspect(f, func(node ast.Node) bool {
  93. switch node := node.(type) {
  94. case *ast.File:
  95. return true
  96. case *ast.GenDecl:
  97. if node.Tok != token.IMPORT {
  98. return false
  99. }
  100. if node.Lparen == token.NoPos && node.Doc != nil {
  101. skip[node.Specs[0]] = true
  102. }
  103. return false
  104. }
  105. return false
  106. })
  107. for i, imp := range f.Imports {
  108. pos := fset.Position(imp.Pos())
  109. if !IsBlank(imp.Name) {
  110. continue
  111. }
  112. // Only flag the first blank import in a group of imports,
  113. // or don't flag any of them, if the first one is
  114. // commented
  115. if i > 0 {
  116. prev := f.Imports[i-1]
  117. prevPos := fset.Position(prev.Pos())
  118. if pos.Line-1 == prevPos.Line && IsBlank(prev.Name) {
  119. continue
  120. }
  121. }
  122. if imp.Doc == nil && imp.Comment == nil && !skip[imp] {
  123. ReportNodef(pass, imp, "a blank import should be only in a main or test package, or have a comment justifying it")
  124. }
  125. }
  126. }
  127. return nil, nil
  128. }
  129. func CheckIncDec(pass *analysis.Pass) (interface{}, error) {
  130. // TODO(dh): this can be noisy for function bodies that look like this:
  131. // x += 3
  132. // ...
  133. // x += 2
  134. // ...
  135. // x += 1
  136. fn := func(node ast.Node) {
  137. assign := node.(*ast.AssignStmt)
  138. if assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN {
  139. return
  140. }
  141. if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
  142. !IsIntLiteral(assign.Rhs[0], "1") {
  143. return
  144. }
  145. suffix := ""
  146. switch assign.Tok {
  147. case token.ADD_ASSIGN:
  148. suffix = "++"
  149. case token.SUB_ASSIGN:
  150. suffix = "--"
  151. }
  152. ReportNodef(pass, assign, "should replace %s with %s%s", Render(pass, assign), Render(pass, assign.Lhs[0]), suffix)
  153. }
  154. pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder([]ast.Node{(*ast.AssignStmt)(nil)}, fn)
  155. return nil, nil
  156. }
  157. func CheckErrorReturn(pass *analysis.Pass) (interface{}, error) {
  158. fnLoop:
  159. for _, fn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
  160. sig := fn.Type().(*types.Signature)
  161. rets := sig.Results()
  162. if rets == nil || rets.Len() < 2 {
  163. continue
  164. }
  165. if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() {
  166. // Last return type is error. If the function also returns
  167. // errors in other positions, that's fine.
  168. continue
  169. }
  170. for i := rets.Len() - 2; i >= 0; i-- {
  171. if rets.At(i).Type() == types.Universe.Lookup("error").Type() {
  172. pass.Reportf(rets.At(i).Pos(), "error should be returned as the last argument")
  173. continue fnLoop
  174. }
  175. }
  176. }
  177. return nil, nil
  178. }
  179. // CheckUnexportedReturn checks that exported functions on exported
  180. // types do not return unexported types.
  181. func CheckUnexportedReturn(pass *analysis.Pass) (interface{}, error) {
  182. for _, fn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
  183. if fn.Synthetic != "" || fn.Parent() != nil {
  184. continue
  185. }
  186. if !ast.IsExported(fn.Name()) || IsInMain(pass, fn) || IsInTest(pass, fn) {
  187. continue
  188. }
  189. sig := fn.Type().(*types.Signature)
  190. if sig.Recv() != nil && !ast.IsExported(Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
  191. continue
  192. }
  193. res := sig.Results()
  194. for i := 0; i < res.Len(); i++ {
  195. if named, ok := DereferenceR(res.At(i).Type()).(*types.Named); ok &&
  196. !ast.IsExported(named.Obj().Name()) &&
  197. named != types.Universe.Lookup("error").Type() {
  198. pass.Reportf(fn.Pos(), "should not return unexported type")
  199. }
  200. }
  201. }
  202. return nil, nil
  203. }
  204. func CheckReceiverNames(pass *analysis.Pass) (interface{}, error) {
  205. ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg
  206. for _, m := range ssapkg.Members {
  207. if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
  208. ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
  209. for _, sel := range ms {
  210. fn := sel.Obj().(*types.Func)
  211. recv := fn.Type().(*types.Signature).Recv()
  212. if Dereference(recv.Type()) != T.Type() {
  213. // skip embedded methods
  214. continue
  215. }
  216. if recv.Name() == "self" || recv.Name() == "this" {
  217. ReportfFG(pass, recv.Pos(), `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`)
  218. }
  219. if recv.Name() == "_" {
  220. ReportfFG(pass, recv.Pos(), "receiver name should not be an underscore, omit the name if it is unused")
  221. }
  222. }
  223. }
  224. }
  225. return nil, nil
  226. }
  227. func CheckReceiverNamesIdentical(pass *analysis.Pass) (interface{}, error) {
  228. ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg
  229. for _, m := range ssapkg.Members {
  230. names := map[string]int{}
  231. var firstFn *types.Func
  232. if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
  233. ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
  234. for _, sel := range ms {
  235. fn := sel.Obj().(*types.Func)
  236. recv := fn.Type().(*types.Signature).Recv()
  237. if Dereference(recv.Type()) != T.Type() {
  238. // skip embedded methods
  239. continue
  240. }
  241. if firstFn == nil {
  242. firstFn = fn
  243. }
  244. if recv.Name() != "" && recv.Name() != "_" {
  245. names[recv.Name()]++
  246. }
  247. }
  248. }
  249. if len(names) > 1 {
  250. var seen []string
  251. for name, count := range names {
  252. seen = append(seen, fmt.Sprintf("%dx %q", count, name))
  253. }
  254. pass.Reportf(firstFn.Pos(), "methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", "))
  255. }
  256. }
  257. return nil, nil
  258. }
  259. func CheckContextFirstArg(pass *analysis.Pass) (interface{}, error) {
  260. // TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
  261. // func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
  262. fnLoop:
  263. for _, fn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
  264. if fn.Synthetic != "" || fn.Parent() != nil {
  265. continue
  266. }
  267. params := fn.Signature.Params()
  268. if params.Len() < 2 {
  269. continue
  270. }
  271. if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
  272. continue
  273. }
  274. for i := 1; i < params.Len(); i++ {
  275. param := params.At(i)
  276. if types.TypeString(param.Type(), nil) == "context.Context" {
  277. pass.Reportf(param.Pos(), "context.Context should be the first argument of a function")
  278. continue fnLoop
  279. }
  280. }
  281. }
  282. return nil, nil
  283. }
  284. func CheckErrorStrings(pass *analysis.Pass) (interface{}, error) {
  285. objNames := map[*ssa.Package]map[string]bool{}
  286. ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg
  287. objNames[ssapkg] = map[string]bool{}
  288. for _, m := range ssapkg.Members {
  289. if typ, ok := m.(*ssa.Type); ok {
  290. objNames[ssapkg][typ.Name()] = true
  291. }
  292. }
  293. for _, fn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
  294. objNames[fn.Package()][fn.Name()] = true
  295. }
  296. for _, fn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs {
  297. if IsInTest(pass, fn) {
  298. // We don't care about malformed error messages in tests;
  299. // they're usually for direct human consumption, not part
  300. // of an API
  301. continue
  302. }
  303. for _, block := range fn.Blocks {
  304. instrLoop:
  305. for _, ins := range block.Instrs {
  306. call, ok := ins.(*ssa.Call)
  307. if !ok {
  308. continue
  309. }
  310. if !IsCallTo(call.Common(), "errors.New") && !IsCallTo(call.Common(), "fmt.Errorf") {
  311. continue
  312. }
  313. k, ok := call.Common().Args[0].(*ssa.Const)
  314. if !ok {
  315. continue
  316. }
  317. s := constant.StringVal(k.Value)
  318. if len(s) == 0 {
  319. continue
  320. }
  321. switch s[len(s)-1] {
  322. case '.', ':', '!', '\n':
  323. pass.Reportf(call.Pos(), "error strings should not end with punctuation or a newline")
  324. }
  325. idx := strings.IndexByte(s, ' ')
  326. if idx == -1 {
  327. // single word error message, probably not a real
  328. // error but something used in tests or during
  329. // debugging
  330. continue
  331. }
  332. word := s[:idx]
  333. first, n := utf8.DecodeRuneInString(word)
  334. if !unicode.IsUpper(first) {
  335. continue
  336. }
  337. for _, c := range word[n:] {
  338. if unicode.IsUpper(c) {
  339. // Word is probably an initialism or
  340. // multi-word function name
  341. continue instrLoop
  342. }
  343. }
  344. word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
  345. if objNames[fn.Package()][word] {
  346. // Word is probably the name of a function or type in this package
  347. continue
  348. }
  349. // First word in error starts with a capital
  350. // letter, and the word doesn't contain any other
  351. // capitals, making it unlikely to be an
  352. // initialism or multi-word function name.
  353. //
  354. // It could still be a proper noun, though.
  355. pass.Reportf(call.Pos(), "error strings should not be capitalized")
  356. }
  357. }
  358. }
  359. return nil, nil
  360. }
  361. func CheckTimeNames(pass *analysis.Pass) (interface{}, error) {
  362. suffixes := []string{
  363. "Sec", "Secs", "Seconds",
  364. "Msec", "Msecs",
  365. "Milli", "Millis", "Milliseconds",
  366. "Usec", "Usecs", "Microseconds",
  367. "MS", "Ms",
  368. }
  369. fn := func(T types.Type, names []*ast.Ident) {
  370. if !IsType(T, "time.Duration") && !IsType(T, "*time.Duration") {
  371. return
  372. }
  373. for _, name := range names {
  374. for _, suffix := range suffixes {
  375. if strings.HasSuffix(name.Name, suffix) {
  376. ReportNodef(pass, name, "var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix)
  377. break
  378. }
  379. }
  380. }
  381. }
  382. for _, f := range pass.Files {
  383. ast.Inspect(f, func(node ast.Node) bool {
  384. switch node := node.(type) {
  385. case *ast.ValueSpec:
  386. T := pass.TypesInfo.TypeOf(node.Type)
  387. fn(T, node.Names)
  388. case *ast.FieldList:
  389. for _, field := range node.List {
  390. T := pass.TypesInfo.TypeOf(field.Type)
  391. fn(T, field.Names)
  392. }
  393. }
  394. return true
  395. })
  396. }
  397. return nil, nil
  398. }
  399. func CheckErrorVarNames(pass *analysis.Pass) (interface{}, error) {
  400. for _, f := range pass.Files {
  401. for _, decl := range f.Decls {
  402. gen, ok := decl.(*ast.GenDecl)
  403. if !ok || gen.Tok != token.VAR {
  404. continue
  405. }
  406. for _, spec := range gen.Specs {
  407. spec := spec.(*ast.ValueSpec)
  408. if len(spec.Names) != len(spec.Values) {
  409. continue
  410. }
  411. for i, name := range spec.Names {
  412. val := spec.Values[i]
  413. if !IsCallToAST(pass, val, "errors.New") && !IsCallToAST(pass, val, "fmt.Errorf") {
  414. continue
  415. }
  416. prefix := "err"
  417. if name.IsExported() {
  418. prefix = "Err"
  419. }
  420. if !strings.HasPrefix(name.Name, prefix) {
  421. ReportNodef(pass, name, "error var %s should have name of the form %sFoo", name.Name, prefix)
  422. }
  423. }
  424. }
  425. }
  426. }
  427. return nil, nil
  428. }
  429. var httpStatusCodes = map[int]string{
  430. 100: "StatusContinue",
  431. 101: "StatusSwitchingProtocols",
  432. 102: "StatusProcessing",
  433. 200: "StatusOK",
  434. 201: "StatusCreated",
  435. 202: "StatusAccepted",
  436. 203: "StatusNonAuthoritativeInfo",
  437. 204: "StatusNoContent",
  438. 205: "StatusResetContent",
  439. 206: "StatusPartialContent",
  440. 207: "StatusMultiStatus",
  441. 208: "StatusAlreadyReported",
  442. 226: "StatusIMUsed",
  443. 300: "StatusMultipleChoices",
  444. 301: "StatusMovedPermanently",
  445. 302: "StatusFound",
  446. 303: "StatusSeeOther",
  447. 304: "StatusNotModified",
  448. 305: "StatusUseProxy",
  449. 307: "StatusTemporaryRedirect",
  450. 308: "StatusPermanentRedirect",
  451. 400: "StatusBadRequest",
  452. 401: "StatusUnauthorized",
  453. 402: "StatusPaymentRequired",
  454. 403: "StatusForbidden",
  455. 404: "StatusNotFound",
  456. 405: "StatusMethodNotAllowed",
  457. 406: "StatusNotAcceptable",
  458. 407: "StatusProxyAuthRequired",
  459. 408: "StatusRequestTimeout",
  460. 409: "StatusConflict",
  461. 410: "StatusGone",
  462. 411: "StatusLengthRequired",
  463. 412: "StatusPreconditionFailed",
  464. 413: "StatusRequestEntityTooLarge",
  465. 414: "StatusRequestURITooLong",
  466. 415: "StatusUnsupportedMediaType",
  467. 416: "StatusRequestedRangeNotSatisfiable",
  468. 417: "StatusExpectationFailed",
  469. 418: "StatusTeapot",
  470. 422: "StatusUnprocessableEntity",
  471. 423: "StatusLocked",
  472. 424: "StatusFailedDependency",
  473. 426: "StatusUpgradeRequired",
  474. 428: "StatusPreconditionRequired",
  475. 429: "StatusTooManyRequests",
  476. 431: "StatusRequestHeaderFieldsTooLarge",
  477. 451: "StatusUnavailableForLegalReasons",
  478. 500: "StatusInternalServerError",
  479. 501: "StatusNotImplemented",
  480. 502: "StatusBadGateway",
  481. 503: "StatusServiceUnavailable",
  482. 504: "StatusGatewayTimeout",
  483. 505: "StatusHTTPVersionNotSupported",
  484. 506: "StatusVariantAlsoNegotiates",
  485. 507: "StatusInsufficientStorage",
  486. 508: "StatusLoopDetected",
  487. 510: "StatusNotExtended",
  488. 511: "StatusNetworkAuthenticationRequired",
  489. }
  490. func CheckHTTPStatusCodes(pass *analysis.Pass) (interface{}, error) {
  491. whitelist := map[string]bool{}
  492. for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
  493. whitelist[code] = true
  494. }
  495. fn := func(node ast.Node) bool {
  496. if node == nil {
  497. return true
  498. }
  499. call, ok := node.(*ast.CallExpr)
  500. if !ok {
  501. return true
  502. }
  503. var arg int
  504. switch CallNameAST(pass, call) {
  505. case "net/http.Error":
  506. arg = 2
  507. case "net/http.Redirect":
  508. arg = 3
  509. case "net/http.StatusText":
  510. arg = 0
  511. case "net/http.RedirectHandler":
  512. arg = 1
  513. default:
  514. return true
  515. }
  516. lit, ok := call.Args[arg].(*ast.BasicLit)
  517. if !ok {
  518. return true
  519. }
  520. if whitelist[lit.Value] {
  521. return true
  522. }
  523. n, err := strconv.Atoi(lit.Value)
  524. if err != nil {
  525. return true
  526. }
  527. s, ok := httpStatusCodes[n]
  528. if !ok {
  529. return true
  530. }
  531. ReportNodefFG(pass, lit, "should use constant http.%s instead of numeric literal %d", s, n)
  532. return true
  533. }
  534. // OPT(dh): replace with inspector
  535. for _, f := range pass.Files {
  536. ast.Inspect(f, fn)
  537. }
  538. return nil, nil
  539. }
  540. func CheckDefaultCaseOrder(pass *analysis.Pass) (interface{}, error) {
  541. fn := func(node ast.Node) {
  542. stmt := node.(*ast.SwitchStmt)
  543. list := stmt.Body.List
  544. for i, c := range list {
  545. if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
  546. ReportNodefFG(pass, c, "default case should be first or last in switch statement")
  547. break
  548. }
  549. }
  550. }
  551. pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder([]ast.Node{(*ast.SwitchStmt)(nil)}, fn)
  552. return nil, nil
  553. }
  554. func CheckYodaConditions(pass *analysis.Pass) (interface{}, error) {
  555. fn := func(node ast.Node) {
  556. cond := node.(*ast.BinaryExpr)
  557. if cond.Op != token.EQL && cond.Op != token.NEQ {
  558. return
  559. }
  560. if _, ok := cond.X.(*ast.BasicLit); !ok {
  561. return
  562. }
  563. if _, ok := cond.Y.(*ast.BasicLit); ok {
  564. // Don't flag lit == lit conditions, just in case
  565. return
  566. }
  567. ReportNodefFG(pass, cond, "don't use Yoda conditions")
  568. }
  569. pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder([]ast.Node{(*ast.BinaryExpr)(nil)}, fn)
  570. return nil, nil
  571. }
  572. func CheckInvisibleCharacters(pass *analysis.Pass) (interface{}, error) {
  573. fn := func(node ast.Node) {
  574. lit := node.(*ast.BasicLit)
  575. if lit.Kind != token.STRING {
  576. return
  577. }
  578. for _, r := range lit.Value {
  579. if unicode.Is(unicode.Cf, r) {
  580. ReportNodef(pass, lit, "string literal contains the Unicode format character %U, consider using the %q escape sequence", r, r)
  581. } else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
  582. ReportNodef(pass, lit, "string literal contains the Unicode control character %U, consider using the %q escape sequence", r, r)
  583. }
  584. }
  585. }
  586. pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder([]ast.Node{(*ast.BasicLit)(nil)}, fn)
  587. return nil, nil
  588. }