msgs.nim 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2013 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. import
  10. std/[strutils, os, tables, terminal, macros, times],
  11. std/private/miscdollars,
  12. options, lineinfos, pathutils
  13. import ropes except `%`
  14. when defined(nimPreviewSlimSystem):
  15. import std/[syncio, assertions]
  16. type InstantiationInfo* = typeof(instantiationInfo())
  17. template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
  18. template toStdOrrKind(stdOrr): untyped =
  19. if stdOrr == stdout: stdOrrStdout else: stdOrrStderr
  20. proc toLowerAscii(a: var string) {.inline.} =
  21. for c in mitems(a):
  22. if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8)
  23. proc flushDot*(conf: ConfigRef) =
  24. ## safe to call multiple times
  25. # xxx one edge case not yet handled is when `printf` is called at CT with `compiletimeFFI`.
  26. let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
  27. let stdOrrKind = toStdOrrKind(stdOrr)
  28. if stdOrrKind in conf.lastMsgWasDot:
  29. conf.lastMsgWasDot.excl stdOrrKind
  30. write(stdOrr, "\n")
  31. proc toCChar*(c: char; result: var string) {.inline.} =
  32. case c
  33. of '\0'..'\x1F', '\x7F'..'\xFF':
  34. result.add '\\'
  35. result.add toOctal(c)
  36. of '\'', '\"', '\\', '?':
  37. result.add '\\'
  38. result.add c
  39. else:
  40. result.add c
  41. proc makeCString*(s: string): Rope =
  42. result = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
  43. result.add("\"")
  44. for i in 0..<s.len:
  45. # line wrapping of string litterals in cgen'd code was a bad idea, e.g. causes: bug #16265
  46. # It also makes reading c sources or grepping harder, for zero benefit.
  47. # const MaxLineLength = 64
  48. # if (i + 1) mod MaxLineLength == 0:
  49. # res.add("\"\L\"")
  50. toCChar(s[i], result)
  51. result.add('\"')
  52. proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
  53. result.fullPath = fullPath
  54. #shallow(result.fullPath)
  55. result.projPath = projPath
  56. #shallow(result.projPath)
  57. result.shortName = fullPath.extractFilename
  58. result.quotedName = result.shortName.makeCString
  59. result.quotedFullName = fullPath.string.makeCString
  60. result.lines = @[]
  61. when defined(nimpretty):
  62. if not result.fullPath.isEmpty:
  63. try:
  64. result.fullContent = readFile(result.fullPath.string)
  65. except IOError:
  66. #rawMessage(errCannotOpenFile, result.fullPath)
  67. # XXX fixme
  68. result.fullContent = ""
  69. when defined(nimpretty):
  70. proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string =
  71. substr(conf.m.fileInfos[fid.int].fullContent, a, b)
  72. proc canonicalCase(path: var string) {.inline.} =
  73. ## the idea is to only use this for checking whether a path is already in
  74. ## the table but otherwise keep the original case
  75. when FileSystemCaseSensitive: discard
  76. else: toLowerAscii(path)
  77. proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
  78. var
  79. canon: AbsoluteFile
  80. try:
  81. canon = canonicalizePath(conf, filename)
  82. except OSError:
  83. canon = filename
  84. canon.string.canonicalCase
  85. result = conf.m.filenameToIndexTbl.hasKey(canon.string)
  86. proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex =
  87. var
  88. canon: AbsoluteFile
  89. pseudoPath = false
  90. try:
  91. canon = canonicalizePath(conf, filename)
  92. except OSError:
  93. canon = filename
  94. # The compiler uses "filenames" such as `command line` or `stdin`
  95. # This flag indicates that we are working with such a path here
  96. pseudoPath = true
  97. var canon2 = canon.string
  98. canon2.canonicalCase
  99. if conf.m.filenameToIndexTbl.hasKey(canon2):
  100. isKnownFile = true
  101. result = conf.m.filenameToIndexTbl[canon2]
  102. else:
  103. isKnownFile = false
  104. result = conf.m.fileInfos.len.FileIndex
  105. conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename
  106. else: relativeTo(canon, conf.projectPath)))
  107. conf.m.filenameToIndexTbl[canon2] = result
  108. proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
  109. var dummy: bool = false
  110. result = fileInfoIdx(conf, filename, dummy)
  111. proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile; isKnownFile: var bool): FileIndex =
  112. fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
  113. proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
  114. var dummy: bool = false
  115. fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
  116. proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
  117. result.fileIndex = fileInfoIdx
  118. if line < int high(uint16):
  119. result.line = uint16(line)
  120. else:
  121. result.line = high(uint16)
  122. if col < int high(int16):
  123. result.col = int16(col)
  124. else:
  125. result.col = -1
  126. proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} =
  127. result = newLineInfo(fileInfoIdx(conf, filename), line, col)
  128. const gCmdLineInfo* = newLineInfo(commandLineIdx, 1, 1)
  129. proc concat(strings: openArray[string]): string =
  130. var totalLen = 0
  131. for s in strings: totalLen += s.len
  132. result = newStringOfCap totalLen
  133. for s in strings: result.add s
  134. proc suggestWriteln*(conf: ConfigRef; s: string) =
  135. if eStdOut in conf.m.errorOutputs:
  136. if isNil(conf.writelnHook):
  137. writeLine(stdout, s)
  138. flushFile(stdout)
  139. else:
  140. conf.writelnHook(s)
  141. proc msgQuit*(x: int8) = quit x
  142. proc msgQuit*(x: string) = quit x
  143. proc suggestQuit*() =
  144. raise newException(ESuggestDone, "suggest done")
  145. # this format is understood by many text editors: it is the same that
  146. # Borland and Freepascal use
  147. const
  148. KindFormat = " [$1]"
  149. KindColor = fgCyan
  150. ErrorTitle = "Error: "
  151. ErrorColor = fgRed
  152. WarningTitle = "Warning: "
  153. WarningColor = fgYellow
  154. HintTitle = "Hint: "
  155. HintColor = fgGreen
  156. # NOTE: currently line info line numbers start with 1,
  157. # but column numbers start with 0, however most editors expect
  158. # first column to be 1, so we need to +1 here
  159. ColOffset* = 1
  160. commandLineDesc* = "command line"
  161. proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len
  162. proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L)
  163. proc pushInfoContext*(conf: ConfigRef; info: TLineInfo; detail: string = "") =
  164. conf.m.msgContext.add((info, detail))
  165. proc popInfoContext*(conf: ConfigRef) =
  166. setLen(conf.m.msgContext, conf.m.msgContext.len - 1)
  167. proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
  168. let i = if index < 0: conf.m.msgContext.len + index else: index
  169. if i >=% conf.m.msgContext.len: result = unknownLineInfo
  170. else: result = conf.m.msgContext[i].info
  171. template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
  172. if fileIdx.int32 < 0 or conf == nil:
  173. (if fileIdx == commandLineIdx: commandLineDesc else: "???")
  174. else:
  175. conf.m.fileInfos[fileIdx.int32].shortName
  176. proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
  177. if fileIdx.int32 < 0 or conf == nil:
  178. (if fileIdx == commandLineIdx: commandLineDesc else: "???")
  179. else: conf.m.fileInfos[fileIdx.int32].projPath.string
  180. proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
  181. if fileIdx.int32 < 0 or conf == nil:
  182. result = (if fileIdx == commandLineIdx: commandLineDesc else: "???")
  183. else:
  184. result = conf.m.fileInfos[fileIdx.int32].fullPath.string
  185. proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
  186. assert fileIdx.int32 >= 0
  187. conf.m.fileInfos[fileIdx.int32].dirtyFile = filename
  188. setLen conf.m.fileInfos[fileIdx.int32].lines, 0
  189. proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
  190. assert fileIdx.int32 >= 0
  191. when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
  192. conf.m.fileInfos[fileIdx.int32].hash = hash
  193. else:
  194. shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
  195. proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
  196. assert fileIdx.int32 >= 0
  197. when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
  198. result = conf.m.fileInfos[fileIdx.int32].hash
  199. else:
  200. shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
  201. proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
  202. if fileIdx.int32 < 0:
  203. result = AbsoluteFile(if fileIdx == commandLineIdx: commandLineDesc else: "???")
  204. elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
  205. result = conf.m.fileInfos[fileIdx.int32].dirtyFile
  206. else:
  207. result = conf.m.fileInfos[fileIdx.int32].fullPath
  208. template toFilename*(conf: ConfigRef; info: TLineInfo): string =
  209. toFilename(conf, info.fileIndex)
  210. template toProjPath*(conf: ConfigRef; info: TLineInfo): string =
  211. toProjPath(conf, info.fileIndex)
  212. template toFullPath*(conf: ConfigRef; info: TLineInfo): string =
  213. toFullPath(conf, info.fileIndex)
  214. template toFullPathConsiderDirty*(conf: ConfigRef; info: TLineInfo): string =
  215. string toFullPathConsiderDirty(conf, info.fileIndex)
  216. proc toFilenameOption*(conf: ConfigRef, fileIdx: FileIndex, opt: FilenameOption): string =
  217. case opt
  218. of foAbs: result = toFullPath(conf, fileIdx)
  219. of foRelProject: result = toProjPath(conf, fileIdx)
  220. of foCanonical:
  221. let absPath = toFullPath(conf, fileIdx)
  222. result = canonicalImportAux(conf, absPath.AbsoluteFile)
  223. of foName: result = toProjPath(conf, fileIdx).lastPathPart
  224. of foLegacyRelProj:
  225. let
  226. absPath = toFullPath(conf, fileIdx)
  227. relPath = toProjPath(conf, fileIdx)
  228. result = if (relPath.len > absPath.len) or (relPath.count("..") > 2):
  229. absPath
  230. else:
  231. relPath
  232. of foStacktrace:
  233. if optExcessiveStackTrace in conf.globalOptions:
  234. result = toFilenameOption(conf, fileIdx, foAbs)
  235. else:
  236. result = toFilenameOption(conf, fileIdx, foName)
  237. proc toMsgFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
  238. toFilenameOption(conf, fileIdx, conf.filenameOption)
  239. template toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
  240. toMsgFilename(conf, info.fileIndex)
  241. proc toLinenumber*(info: TLineInfo): int {.inline.} =
  242. result = int info.line
  243. proc toColumn*(info: TLineInfo): int {.inline.} =
  244. result = info.col
  245. proc toFileLineCol(info: InstantiationInfo): string {.inline.} =
  246. result.toLocation(info.filename, info.line, info.column + ColOffset)
  247. proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
  248. result.toLocation(toMsgFilename(conf, info), info.line.int, info.col.int + ColOffset)
  249. proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info)
  250. proc `$`*(info: TLineInfo): string {.error.} = discard
  251. proc `??`* (conf: ConfigRef; info: TLineInfo, filename: string): bool =
  252. # only for debugging purposes
  253. result = filename in toFilename(conf, info)
  254. type
  255. MsgFlag* = enum ## flags altering msgWriteln behavior
  256. msgStdout, ## force writing to stdout, even stderr is default
  257. msgSkipHook ## skip message hook even if it is present
  258. msgNoUnitSep ## the message is a complete "paragraph".
  259. MsgFlags* = set[MsgFlag]
  260. proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
  261. ## Writes given message string to stderr by default.
  262. ## If ``--stdout`` option is given, writes to stdout instead. If message hook
  263. ## is present, then it is used to output message rather than stderr/stdout.
  264. ## This behavior can be altered by given optional flags.
  265. ## This is used for 'nim dump' etc. where we don't have nimsuggest
  266. ## support.
  267. #if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
  268. let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
  269. if not isNil(conf.writelnHook) and msgSkipHook notin flags:
  270. conf.writelnHook(s & sep)
  271. elif optStdout in conf.globalOptions or msgStdout in flags:
  272. if eStdOut in conf.m.errorOutputs:
  273. flushDot(conf)
  274. write stdout, s
  275. writeLine(stdout, sep)
  276. flushFile(stdout)
  277. else:
  278. if eStdErr in conf.m.errorOutputs:
  279. flushDot(conf)
  280. write stderr, s
  281. writeLine(stderr, sep)
  282. # On Windows stderr is fully-buffered when piped, regardless of C std.
  283. when defined(windows):
  284. flushFile(stderr)
  285. macro callIgnoringStyle(theProc: typed, first: typed,
  286. args: varargs[typed]): untyped =
  287. let typForegroundColor = bindSym"ForegroundColor".getType
  288. let typBackgroundColor = bindSym"BackgroundColor".getType
  289. let typStyle = bindSym"Style".getType
  290. let typTerminalCmd = bindSym"TerminalCmd".getType
  291. result = newCall(theProc)
  292. if first.kind != nnkNilLit: result.add(first)
  293. for arg in children(args[0][1]):
  294. if arg.kind == nnkNilLit: continue
  295. let typ = arg.getType
  296. if typ.kind != nnkEnumTy or
  297. typ != typForegroundColor and
  298. typ != typBackgroundColor and
  299. typ != typStyle and
  300. typ != typTerminalCmd:
  301. result.add(arg)
  302. macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
  303. result = newCall(bindSym"styledWriteLine")
  304. result.add(bindSym"stderr")
  305. for arg in children(args[0][1]):
  306. result.add(arg)
  307. when false:
  308. # not needed because styledWriteLine already ends with resetAttributes
  309. result = newStmtList(result, newCall(bindSym"resetAttributes", bindSym"stderr"))
  310. template callWritelnHook(args: varargs[string, `$`]) =
  311. conf.writelnHook concat(args)
  312. proc msgWrite(conf: ConfigRef; s: string) =
  313. if conf.m.errorOutputs != {}:
  314. let stdOrr =
  315. if optStdout in conf.globalOptions:
  316. stdout
  317. else:
  318. stderr
  319. write(stdOrr, s)
  320. flushFile(stdOrr)
  321. conf.lastMsgWasDot.incl stdOrr.toStdOrrKind() # subsequent writes need `flushDot`
  322. template styledMsgWriteln(args: varargs[typed]) =
  323. if not isNil(conf.writelnHook):
  324. callIgnoringStyle(callWritelnHook, nil, args)
  325. elif optStdout in conf.globalOptions:
  326. if eStdOut in conf.m.errorOutputs:
  327. flushDot(conf)
  328. callIgnoringStyle(writeLine, stdout, args)
  329. flushFile(stdout)
  330. elif eStdErr in conf.m.errorOutputs:
  331. flushDot(conf)
  332. if optUseColors in conf.globalOptions:
  333. callStyledWriteLineStderr(args)
  334. else:
  335. callIgnoringStyle(writeLine, stderr, args)
  336. # On Windows stderr is fully-buffered when piped, regardless of C std.
  337. when defined(windows):
  338. flushFile(stderr)
  339. proc msgKindToString*(kind: TMsgKind): string = MsgKindToStr[kind]
  340. # later versions may provide translated error messages
  341. proc getMessageStr(msg: TMsgKind, arg: string): string = msgKindToString(msg) % [arg]
  342. type TErrorHandling* = enum doNothing, doAbort, doRaise
  343. proc log*(s: string) =
  344. var f: File
  345. if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
  346. f.writeLine(s)
  347. close(f)
  348. proc quit(conf: ConfigRef; msg: TMsgKind) {.gcsafe.} =
  349. if conf.isDefined("nimDebug"): quitOrRaise(conf, $msg)
  350. elif defined(debug) or msg == errInternal or conf.hasHint(hintStackTrace):
  351. {.gcsafe.}:
  352. if stackTraceAvailable() and isNil(conf.writelnHook):
  353. writeStackTrace()
  354. else:
  355. styledMsgWriteln(fgRed, """
  356. No stack traceback available
  357. To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 for details""" %
  358. [conf.command, "intern.html#debugging-the-compiler".createDocLink], conf.unitSep)
  359. quit 1
  360. proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
  361. if msg in fatalMsgs:
  362. if conf.cmd == cmdIdeTools: log(s)
  363. if conf.cmd != cmdIdeTools or msg != errFatal:
  364. quit(conf, msg)
  365. if msg >= errMin and msg <= errMax or
  366. (msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
  367. inc(conf.errorCounter)
  368. conf.exitcode = 1'i8
  369. if conf.errorCounter >= conf.errorMax:
  370. # only really quit when we're not in the new 'nim check --def' mode:
  371. if conf.ideCmd == ideNone:
  372. when defined(nimsuggest):
  373. #we need to inform the user that something went wrong when initializing NimSuggest
  374. raiseRecoverableError(s)
  375. else:
  376. quit(conf, msg)
  377. elif eh == doAbort and conf.cmd != cmdIdeTools:
  378. quit(conf, msg)
  379. elif eh == doRaise:
  380. raiseRecoverableError(s)
  381. proc `==`*(a, b: TLineInfo): bool =
  382. result = a.line == b.line and a.fileIndex == b.fileIndex
  383. proc exactEquals*(a, b: TLineInfo): bool =
  384. result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col
  385. proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
  386. const instantiationFrom = "template/generic instantiation from here"
  387. const instantiationOfFrom = "template/generic instantiation of `$1` from here"
  388. var info = lastinfo
  389. for i in 0..<conf.m.msgContext.len:
  390. let context = conf.m.msgContext[i]
  391. if context.info != lastinfo and context.info != info:
  392. if conf.structuredErrorHook != nil:
  393. conf.structuredErrorHook(conf, context.info, instantiationFrom,
  394. Severity.Hint)
  395. else:
  396. let message =
  397. if context.detail == "":
  398. instantiationFrom
  399. else:
  400. instantiationOfFrom.format(context.detail)
  401. styledMsgWriteln(styleBright, conf.toFileLineCol(context.info), " ", resetStyle, message)
  402. info = context.info
  403. proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
  404. msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
  405. proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
  406. conf.m.fileInfos[fileIdx.int32].lines.add line
  407. proc numLines*(conf: ConfigRef, fileIdx: FileIndex): int =
  408. ## xxx there's an off by 1 error that should be fixed; if a file ends with "foo" or "foo\n"
  409. ## it will return same number of lines (ie, a trailing empty line is discounted)
  410. result = conf.m.fileInfos[fileIdx.int32].lines.len
  411. if result == 0:
  412. try:
  413. for line in lines(toFullPathConsiderDirty(conf, fileIdx).string):
  414. addSourceLine conf, fileIdx, line
  415. except IOError:
  416. discard
  417. result = conf.m.fileInfos[fileIdx.int32].lines.len
  418. proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
  419. ## 1-based index (matches editor line numbers); 1st line is for i.line = 1
  420. ## last valid line is `numLines` inclusive
  421. if i.fileIndex.int32 < 0: return ""
  422. let num = numLines(conf, i.fileIndex)
  423. # can happen if the error points to EOF:
  424. if i.line.int > num: return ""
  425. result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
  426. proc getSurroundingSrc(conf: ConfigRef; info: TLineInfo): string =
  427. if conf.hasHint(hintSource) and info != unknownLineInfo:
  428. const indent = " "
  429. result = "\n" & indent & $sourceLine(conf, info)
  430. if info.col >= 0:
  431. result.add "\n" & indent & spaces(info.col) & '^'
  432. proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): string =
  433. let title = case msg
  434. of warnMin..warnMax: WarningTitle
  435. of hintMin..hintMax: HintTitle
  436. else: ErrorTitle
  437. conf.toFileLineCol(info) & " " & title & getMessageStr(msg, arg)
  438. proc liMessage*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
  439. eh: TErrorHandling, info2: InstantiationInfo, isRaw = false,
  440. ignoreError = false) {.gcsafe, noinline.} =
  441. var
  442. title: string
  443. color: ForegroundColor
  444. ignoreMsg = false
  445. sev: Severity
  446. let errorOutputsOld = conf.m.errorOutputs
  447. if msg in fatalMsgs:
  448. # don't gag, refs bug #7080, bug #18278; this can happen with `{.fatal.}`
  449. # or inside a `tryConstExpr`.
  450. conf.m.errorOutputs = {eStdOut, eStdErr}
  451. let kind = if msg in warnMin..hintMax and msg != hintUserRaw: $msg else: "" # xxx not sure why hintUserRaw is special
  452. case msg
  453. of errMin..errMax:
  454. sev = Severity.Error
  455. writeContext(conf, info)
  456. title = ErrorTitle
  457. color = ErrorColor
  458. when false:
  459. # we try to filter error messages so that not two error message
  460. # in the same file and line are produced:
  461. # xxx `lastError` is only used in this disabled code; but could be useful to revive
  462. ignoreMsg = conf.m.lastError == info and info != unknownLineInfo and eh != doAbort
  463. if info != unknownLineInfo: conf.m.lastError = info
  464. of warnMin..warnMax:
  465. sev = Severity.Warning
  466. ignoreMsg = not conf.hasWarn(msg)
  467. if not ignoreMsg and msg in conf.warningAsErrors:
  468. title = ErrorTitle
  469. color = ErrorColor
  470. else:
  471. title = WarningTitle
  472. color = WarningColor
  473. if not ignoreMsg: writeContext(conf, info)
  474. inc(conf.warnCounter)
  475. of hintMin..hintMax:
  476. sev = Severity.Hint
  477. ignoreMsg = not conf.hasHint(msg)
  478. if not ignoreMsg and msg in conf.warningAsErrors:
  479. title = ErrorTitle
  480. color = ErrorColor
  481. else:
  482. title = HintTitle
  483. color = HintColor
  484. inc(conf.hintCounter)
  485. let s = if isRaw: arg else: getMessageStr(msg, arg)
  486. if not ignoreMsg:
  487. let loc = if info != unknownLineInfo: conf.toFileLineCol(info) & " " else: ""
  488. # we could also show `conf.cmdInput` here for `projectIsCmd`
  489. var kindmsg = if kind.len > 0: KindFormat % kind else: ""
  490. if conf.structuredErrorHook != nil:
  491. conf.structuredErrorHook(conf, info, s & kindmsg, sev)
  492. if not ignoreMsgBecauseOfIdeTools(conf, msg):
  493. if msg == hintProcessing and conf.hintProcessingDots:
  494. msgWrite(conf, ".")
  495. else:
  496. styledMsgWriteln(styleBright, loc, resetStyle, color, title, resetStyle, s, KindColor, kindmsg,
  497. resetStyle, conf.getSurroundingSrc(info), conf.unitSep)
  498. if hintMsgOrigin in conf.mainPackageNotes:
  499. # xxx needs a bit of refactoring to honor `conf.filenameOption`
  500. styledMsgWriteln(styleBright, toFileLineCol(info2), resetStyle,
  501. " compiler msg initiated here", KindColor,
  502. KindFormat % $hintMsgOrigin,
  503. resetStyle, conf.unitSep)
  504. if not ignoreError:
  505. handleError(conf, msg, eh, s, ignoreMsg)
  506. if msg in fatalMsgs:
  507. # most likely would have died here but just in case, we restore state
  508. conf.m.errorOutputs = errorOutputsOld
  509. template rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
  510. let arg = msgKindToString(msg) % args
  511. liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc(), isRaw = true)
  512. template rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
  513. liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc())
  514. template fatal*(conf: ConfigRef; info: TLineInfo, arg = "", msg = errFatal) =
  515. liMessage(conf, info, msg, arg, doAbort, instLoc())
  516. template globalAssert*(conf: ConfigRef; cond: untyped, info: TLineInfo = unknownLineInfo, arg = "") =
  517. ## avoids boilerplate
  518. if not cond:
  519. var arg2 = "'$1' failed" % [astToStr(cond)]
  520. if arg.len > 0: arg2.add "; " & astToStr(arg) & ": " & arg
  521. liMessage(conf, info, errGenerated, arg2, doRaise, instLoc())
  522. template globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  523. ## `local` means compilation keeps going until errorMax is reached (via `doNothing`),
  524. ## `global` means it stops.
  525. liMessage(conf, info, msg, arg, doRaise, instLoc())
  526. template globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
  527. liMessage(conf, info, errGenerated, arg, doRaise, instLoc())
  528. template localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  529. liMessage(conf, info, msg, arg, doNothing, instLoc())
  530. template localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
  531. liMessage(conf, info, errGenerated, arg, doNothing, instLoc())
  532. template message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  533. liMessage(conf, info, msg, arg, doNothing, instLoc())
  534. proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "") {.inline.} =
  535. message(conf, info, warnDeprecated, msg)
  536. proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
  537. if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
  538. writeContext(conf, info)
  539. liMessage(conf, info, errInternal, errMsg, doAbort, info2)
  540. template internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
  541. internalErrorImpl(conf, info, errMsg, instLoc())
  542. template internalError*(conf: ConfigRef; errMsg: string) =
  543. internalErrorImpl(conf, unknownLineInfo, errMsg, instLoc())
  544. template internalAssert*(conf: ConfigRef, e: bool) =
  545. # xxx merge with `globalAssert`
  546. if not e:
  547. const info2 = instLoc()
  548. let arg = info2.toFileLineCol
  549. internalErrorImpl(conf, unknownLineInfo, arg, info2)
  550. template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string, extraMsg = "") =
  551. let m = "'$1' should be: '$2'$3" % [got, beau, extraMsg]
  552. let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
  553. liMessage(conf, info, msg, m, doNothing, instLoc())
  554. proc quotedFilename*(conf: ConfigRef; fi: FileIndex): Rope =
  555. if fi.int32 < 0:
  556. result = makeCString "???"
  557. elif optExcessiveStackTrace in conf.globalOptions:
  558. result = conf.m.fileInfos[fi.int32].quotedFullName
  559. else:
  560. result = conf.m.fileInfos[fi.int32].quotedName
  561. proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope =
  562. quotedFilename(conf, i.fileIndex)
  563. template listMsg(title, r) =
  564. msgWriteln(conf, title, {msgNoUnitSep})
  565. for a in r: msgWriteln(conf, " [$1] $2" % [if a in conf.notes: "x" else: " ", $a], {msgNoUnitSep})
  566. proc listWarnings*(conf: ConfigRef) = listMsg("Warnings:", warnMin..warnMax)
  567. proc listHints*(conf: ConfigRef) = listMsg("Hints:", hintMin..hintMax)
  568. proc uniqueModuleName*(conf: ConfigRef; fid: FileIndex): string =
  569. ## The unique module name is guaranteed to only contain {'A'..'Z', 'a'..'z', '0'..'9', '_'}
  570. ## so that it is useful as a C identifier snippet.
  571. let path = AbsoluteFile toFullPath(conf, fid)
  572. let rel =
  573. if path.string.startsWith(conf.libpath.string):
  574. relativeTo(path, conf.libpath).string
  575. else:
  576. relativeTo(path, conf.projectPath).string
  577. let trunc = if rel.endsWith(".nim"): rel.len - len(".nim") else: rel.len
  578. result = newStringOfCap(trunc)
  579. for i in 0..<trunc:
  580. let c = rel[i]
  581. case c
  582. of 'a'..'z':
  583. result.add c
  584. of {os.DirSep, os.AltSep}:
  585. result.add 'Z' # because it looks a bit like '/'
  586. of '.':
  587. result.add 'O' # a circle
  588. else:
  589. # We mangle upper letters and digits too so that there cannot
  590. # be clashes with our special meanings of 'Z' and 'O'
  591. result.addInt ord(c)
  592. proc genSuccessX*(conf: ConfigRef) =
  593. let mem =
  594. when declared(system.getMaxMem): formatSize(getMaxMem()) & " peakmem"
  595. else: formatSize(getTotalMem()) & " totmem"
  596. let loc = $conf.linesCompiled
  597. var build = ""
  598. var flags = ""
  599. const debugModeHints = "none (DEBUG BUILD, `-d:release` generates faster code)"
  600. if conf.cmd in cmdBackends:
  601. if conf.backend != backendJs:
  602. build.add "mm: $#; " % $conf.selectedGC
  603. if optThreads in conf.globalOptions: build.add "threads: on; "
  604. build.add "opt: "
  605. if optOptimizeSpeed in conf.options: build.add "speed"
  606. elif optOptimizeSize in conf.options: build.add "size"
  607. else: build.add debugModeHints
  608. # pending https://github.com/timotheecour/Nim/issues/752, point to optimization.html
  609. if isDefined(conf, "danger"): flags.add " -d:danger"
  610. elif isDefined(conf, "release"): flags.add " -d:release"
  611. else:
  612. build.add "opt: "
  613. if isDefined(conf, "danger"):
  614. build.add "speed"
  615. flags.add " -d:danger"
  616. elif isDefined(conf, "release"):
  617. build.add "speed"
  618. flags.add " -d:release"
  619. else: build.add debugModeHints
  620. if flags.len > 0: build.add "; options:" & flags
  621. let sec = formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3)
  622. let project = if conf.filenameOption == foAbs: $conf.projectFull else: $conf.projectName
  623. # xxx honor conf.filenameOption more accurately
  624. var output: string
  625. if optCompileOnly in conf.globalOptions and conf.cmd != cmdJsonscript:
  626. output = $conf.jsonBuildFile
  627. elif conf.outFile.isEmpty and conf.cmd notin {cmdJsonscript} + cmdDocLike + cmdBackends:
  628. # for some cmd we expect a valid absOutFile
  629. output = "unknownOutput"
  630. else:
  631. output = $conf.absOutFile
  632. if conf.filenameOption != foAbs: output = output.AbsoluteFile.extractFilename
  633. # xxx honor filenameOption more accurately
  634. rawMessage(conf, hintSuccessX, [
  635. "build", build,
  636. "loc", loc,
  637. "sec", sec,
  638. "mem", mem,
  639. "project", project,
  640. "output", output,
  641. ])