msgs.nim 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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. options, strutils, os, tables, ropes, terminal, macros,
  11. lineinfos, pathutils
  12. import std/private/miscdollars
  13. import strutils2
  14. type InstantiationInfo* = typeof(instantiationInfo())
  15. template instLoc(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
  16. template flushDot(conf, stdorr) =
  17. ## safe to call multiple times
  18. if conf.lastMsgWasDot:
  19. conf.lastMsgWasDot = false
  20. write(stdorr, "\n")
  21. proc toCChar*(c: char; result: var string) =
  22. case c
  23. of '\0'..'\x1F', '\x7F'..'\xFF':
  24. result.add '\\'
  25. result.add toOctal(c)
  26. of '\'', '\"', '\\', '?':
  27. result.add '\\'
  28. result.add c
  29. else:
  30. result.add c
  31. proc makeCString*(s: string): Rope =
  32. const MaxLineLength = 64
  33. result = nil
  34. var res = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
  35. res.add("\"")
  36. for i in 0..<s.len:
  37. if (i + 1) mod MaxLineLength == 0:
  38. res.add("\"\L\"")
  39. toCChar(s[i], res)
  40. res.add('\"')
  41. result.add(rope(res))
  42. proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
  43. result.fullPath = fullPath
  44. #shallow(result.fullPath)
  45. result.projPath = projPath
  46. #shallow(result.projPath)
  47. result.shortName = fullPath.extractFilename
  48. result.quotedName = result.shortName.makeCString
  49. result.quotedFullName = fullPath.string.makeCString
  50. result.lines = @[]
  51. when defined(nimpretty):
  52. if not result.fullPath.isEmpty:
  53. try:
  54. result.fullContent = readFile(result.fullPath.string)
  55. except IOError:
  56. #rawMessage(errCannotOpenFile, result.fullPath)
  57. # XXX fixme
  58. result.fullContent = ""
  59. when defined(nimpretty):
  60. proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string =
  61. substr(conf.m.fileInfos[fid.int].fullContent, a, b)
  62. proc canonicalCase(path: var string) =
  63. ## the idea is to only use this for checking whether a path is already in
  64. ## the table but otherwise keep the original case
  65. when FileSystemCaseSensitive: discard
  66. else: toLowerAscii(path)
  67. proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
  68. var
  69. canon: AbsoluteFile
  70. try:
  71. canon = canonicalizePath(conf, filename)
  72. except OSError:
  73. canon = filename
  74. canon.string.canonicalCase
  75. result = conf.m.filenameToIndexTbl.hasKey(canon.string)
  76. proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex =
  77. var
  78. canon: AbsoluteFile
  79. pseudoPath = false
  80. try:
  81. canon = canonicalizePath(conf, filename)
  82. shallow(canon.string)
  83. except OSError:
  84. canon = filename
  85. # The compiler uses "filenames" such as `command line` or `stdin`
  86. # This flag indicates that we are working with such a path here
  87. pseudoPath = true
  88. var canon2: string
  89. forceCopy(canon2, canon.string) # because `canon` may be shallow
  90. canon2.canonicalCase
  91. if conf.m.filenameToIndexTbl.hasKey(canon2):
  92. isKnownFile = true
  93. result = conf.m.filenameToIndexTbl[canon2]
  94. else:
  95. isKnownFile = false
  96. result = conf.m.fileInfos.len.FileIndex
  97. conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename
  98. else: relativeTo(canon, conf.projectPath)))
  99. conf.m.filenameToIndexTbl[canon2] = result
  100. proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
  101. var dummy: bool
  102. result = fileInfoIdx(conf, filename, dummy)
  103. proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
  104. result.fileIndex = fileInfoIdx
  105. if line < int high(uint16):
  106. result.line = uint16(line)
  107. else:
  108. result.line = high(uint16)
  109. if col < int high(int16):
  110. result.col = int16(col)
  111. else:
  112. result.col = -1
  113. proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} =
  114. result = newLineInfo(fileInfoIdx(conf, filename), line, col)
  115. const gCmdLineInfo* = newLineInfo(commandLineIdx, 1, 1)
  116. proc concat(strings: openArray[string]): string =
  117. var totalLen = 0
  118. for s in strings: totalLen += s.len
  119. result = newStringOfCap totalLen
  120. for s in strings: result.add s
  121. proc suggestWriteln*(conf: ConfigRef; s: string) =
  122. if eStdOut in conf.m.errorOutputs:
  123. if isNil(conf.writelnHook):
  124. writeLine(stdout, s)
  125. flushFile(stdout)
  126. else:
  127. conf.writelnHook(s)
  128. proc msgQuit*(x: int8) = quit x
  129. proc msgQuit*(x: string) = quit x
  130. proc suggestQuit*() =
  131. raise newException(ESuggestDone, "suggest done")
  132. # this format is understood by many text editors: it is the same that
  133. # Borland and Freepascal use
  134. const
  135. KindFormat = " [$1]"
  136. KindColor = fgCyan
  137. ErrorTitle = "Error: "
  138. ErrorColor = fgRed
  139. WarningTitle = "Warning: "
  140. WarningColor = fgYellow
  141. HintTitle = "Hint: "
  142. HintColor = fgGreen
  143. # NOTE: currently line info line numbers start with 1,
  144. # but column numbers start with 0, however most editors expect
  145. # first column to be 1, so we need to +1 here
  146. ColOffset* = 1
  147. commandLineDesc* = "command line"
  148. proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len
  149. proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L)
  150. proc pushInfoContext*(conf: ConfigRef; info: TLineInfo; detail: string = "") =
  151. conf.m.msgContext.add((info, detail))
  152. proc popInfoContext*(conf: ConfigRef) =
  153. setLen(conf.m.msgContext, conf.m.msgContext.len - 1)
  154. proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
  155. let i = if index < 0: conf.m.msgContext.len + index else: index
  156. if i >=% conf.m.msgContext.len: result = unknownLineInfo
  157. else: result = conf.m.msgContext[i].info
  158. template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
  159. if fileIdx.int32 < 0 or conf == nil:
  160. (if fileIdx == commandLineIdx: commandLineDesc else: "???")
  161. else:
  162. conf.m.fileInfos[fileIdx.int32].shortName
  163. proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
  164. if fileIdx.int32 < 0 or conf == nil:
  165. (if fileIdx == commandLineIdx: commandLineDesc else: "???")
  166. else: conf.m.fileInfos[fileIdx.int32].projPath.string
  167. proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
  168. if fileIdx.int32 < 0 or conf == nil:
  169. result = (if fileIdx == commandLineIdx: commandLineDesc else: "???")
  170. else:
  171. result = conf.m.fileInfos[fileIdx.int32].fullPath.string
  172. proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
  173. assert fileIdx.int32 >= 0
  174. conf.m.fileInfos[fileIdx.int32].dirtyFile = filename
  175. setLen conf.m.fileInfos[fileIdx.int32].lines, 0
  176. proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
  177. assert fileIdx.int32 >= 0
  178. shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
  179. proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
  180. assert fileIdx.int32 >= 0
  181. shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
  182. proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
  183. if fileIdx.int32 < 0:
  184. result = AbsoluteFile(if fileIdx == commandLineIdx: commandLineDesc else: "???")
  185. elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
  186. result = conf.m.fileInfos[fileIdx.int32].dirtyFile
  187. else:
  188. result = conf.m.fileInfos[fileIdx.int32].fullPath
  189. template toFilename*(conf: ConfigRef; info: TLineInfo): string =
  190. toFilename(conf, info.fileIndex)
  191. template toProjPath*(conf: ConfigRef; info: TLineInfo): string =
  192. toProjPath(conf, info.fileIndex)
  193. template toFullPath*(conf: ConfigRef; info: TLineInfo): string =
  194. toFullPath(conf, info.fileIndex)
  195. template toFullPathConsiderDirty*(conf: ConfigRef; info: TLineInfo): string =
  196. string toFullPathConsiderDirty(conf, info.fileIndex)
  197. type FilenameOption* = enum
  198. foAbs # absolute path, eg: /pathto/bar/foo.nim
  199. foRelProject # relative to project path, eg: ../foo.nim
  200. foMagicSauce # magic sauce, shortest of (foAbs, foRelProject)
  201. foName # lastPathPart, eg: foo.nim
  202. foShort # foName without extension, eg: foo
  203. foStacktrace # if optExcessiveStackTrace: foAbs else: foName
  204. proc toFilenameOption*(conf: ConfigRef, fileIdx: FileIndex, opt: FilenameOption): string =
  205. case opt
  206. of foAbs: result = toFullPath(conf, fileIdx)
  207. of foRelProject: result = toProjPath(conf, fileIdx)
  208. of foMagicSauce:
  209. let
  210. absPath = toFullPath(conf, fileIdx)
  211. relPath = toProjPath(conf, fileIdx)
  212. result = if (optListFullPaths in conf.globalOptions) or
  213. (relPath.len > absPath.len) or
  214. (relPath.count("..") > 2):
  215. absPath
  216. else:
  217. relPath
  218. of foName: result = toProjPath(conf, fileIdx).lastPathPart
  219. of foShort: result = toFilename(conf, fileIdx)
  220. of foStacktrace:
  221. if optExcessiveStackTrace in conf.globalOptions:
  222. result = toFilenameOption(conf, fileIdx, foAbs)
  223. else:
  224. result = toFilenameOption(conf, fileIdx, foName)
  225. proc toMsgFilename*(conf: ConfigRef; info: FileIndex): string =
  226. toFilenameOption(conf, info, foMagicSauce)
  227. template toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
  228. toMsgFilename(conf, info.fileIndex)
  229. proc toLinenumber*(info: TLineInfo): int {.inline.} =
  230. result = int info.line
  231. proc toColumn*(info: TLineInfo): int {.inline.} =
  232. result = info.col
  233. proc toFileLineCol(info: InstantiationInfo): string {.inline.} =
  234. result.toLocation(info.filename, info.line, info.column + ColOffset)
  235. proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
  236. result.toLocation(toMsgFilename(conf, info), info.line.int, info.col.int + ColOffset)
  237. proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info)
  238. proc `$`*(info: TLineInfo): string {.error.} = discard
  239. proc `??`* (conf: ConfigRef; info: TLineInfo, filename: string): bool =
  240. # only for debugging purposes
  241. result = filename in toFilename(conf, info)
  242. type
  243. MsgFlag* = enum ## flags altering msgWriteln behavior
  244. msgStdout, ## force writing to stdout, even stderr is default
  245. msgSkipHook ## skip message hook even if it is present
  246. MsgFlags* = set[MsgFlag]
  247. proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
  248. ## Writes given message string to stderr by default.
  249. ## If ``--stdout`` option is given, writes to stdout instead. If message hook
  250. ## is present, then it is used to output message rather than stderr/stdout.
  251. ## This behavior can be altered by given optional flags.
  252. ## This is used for 'nim dump' etc. where we don't have nimsuggest
  253. ## support.
  254. #if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
  255. if not isNil(conf.writelnHook) and msgSkipHook notin flags:
  256. conf.writelnHook(s)
  257. elif optStdout in conf.globalOptions or msgStdout in flags:
  258. if eStdOut in conf.m.errorOutputs:
  259. flushDot(conf, stdout)
  260. writeLine(stdout, s)
  261. flushFile(stdout)
  262. else:
  263. if eStdErr in conf.m.errorOutputs:
  264. flushDot(conf, stderr)
  265. writeLine(stderr, s)
  266. # On Windows stderr is fully-buffered when piped, regardless of C std.
  267. when defined(windows):
  268. flushFile(stderr)
  269. macro callIgnoringStyle(theProc: typed, first: typed,
  270. args: varargs[typed]): untyped =
  271. let typForegroundColor = bindSym"ForegroundColor".getType
  272. let typBackgroundColor = bindSym"BackgroundColor".getType
  273. let typStyle = bindSym"Style".getType
  274. let typTerminalCmd = bindSym"TerminalCmd".getType
  275. result = newCall(theProc)
  276. if first.kind != nnkNilLit: result.add(first)
  277. for arg in children(args[0][1]):
  278. if arg.kind == nnkNilLit: continue
  279. let typ = arg.getType
  280. if typ.kind != nnkEnumTy or
  281. typ != typForegroundColor and
  282. typ != typBackgroundColor and
  283. typ != typStyle and
  284. typ != typTerminalCmd:
  285. result.add(arg)
  286. macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
  287. result = newCall(bindSym"styledWriteLine")
  288. result.add(bindSym"stderr")
  289. for arg in children(args[0][1]):
  290. result.add(arg)
  291. when false:
  292. # not needed because styledWriteLine already ends with resetAttributes
  293. result = newStmtList(result, newCall(bindSym"resetAttributes", bindSym"stderr"))
  294. template callWritelnHook(args: varargs[string, `$`]) =
  295. conf.writelnHook concat(args)
  296. proc msgWrite(conf: ConfigRef; s: string) =
  297. if conf.m.errorOutputs != {}:
  298. let stdOrr =
  299. if optStdout in conf.globalOptions:
  300. stdout
  301. else:
  302. stderr
  303. write(stdOrr, s)
  304. flushFile(stdOrr)
  305. conf.lastMsgWasDot = true # subsequent writes need `flushDot`
  306. template styledMsgWriteln*(args: varargs[typed]) =
  307. if not isNil(conf.writelnHook):
  308. callIgnoringStyle(callWritelnHook, nil, args)
  309. elif optStdout in conf.globalOptions:
  310. if eStdOut in conf.m.errorOutputs:
  311. flushDot(conf, stdout)
  312. callIgnoringStyle(writeLine, stdout, args)
  313. flushFile(stdout)
  314. elif eStdErr in conf.m.errorOutputs:
  315. flushDot(conf, stderr)
  316. if optUseColors in conf.globalOptions:
  317. callStyledWriteLineStderr(args)
  318. else:
  319. callIgnoringStyle(writeLine, stderr, args)
  320. # On Windows stderr is fully-buffered when piped, regardless of C std.
  321. when defined(windows):
  322. flushFile(stderr)
  323. proc msgKindToString*(kind: TMsgKind): string = MsgKindToStr[kind]
  324. # later versions may provide translated error messages
  325. proc getMessageStr(msg: TMsgKind, arg: string): string = msgKindToString(msg) % [arg]
  326. type TErrorHandling* = enum doNothing, doAbort, doRaise
  327. proc log*(s: string) =
  328. var f: File
  329. if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
  330. f.writeLine(s)
  331. close(f)
  332. proc quit(conf: ConfigRef; msg: TMsgKind) {.gcsafe.} =
  333. if defined(debug) or msg == errInternal or conf.hasHint(hintStackTrace):
  334. {.gcsafe.}:
  335. if stackTraceAvailable() and isNil(conf.writelnHook):
  336. writeStackTrace()
  337. else:
  338. styledMsgWriteln(fgRed, """
  339. No stack traceback available
  340. To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 for details""" %
  341. [conf.command, "intern.html#debugging-the-compiler".createDocLink])
  342. quit 1
  343. proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string) =
  344. if msg >= fatalMin and msg <= fatalMax:
  345. if conf.cmd == cmdIdeTools: log(s)
  346. quit(conf, msg)
  347. if msg >= errMin and msg <= errMax or
  348. (msg in warnMin..warnMax and msg in conf.warningAsErrors):
  349. inc(conf.errorCounter)
  350. conf.exitcode = 1'i8
  351. if conf.errorCounter >= conf.errorMax:
  352. quit(conf, msg)
  353. elif eh == doAbort and conf.cmd != cmdIdeTools:
  354. quit(conf, msg)
  355. elif eh == doRaise:
  356. raiseRecoverableError(s)
  357. proc `==`*(a, b: TLineInfo): bool =
  358. result = a.line == b.line and a.fileIndex == b.fileIndex
  359. proc exactEquals*(a, b: TLineInfo): bool =
  360. result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col
  361. proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
  362. const instantiationFrom = "template/generic instantiation from here"
  363. const instantiationOfFrom = "template/generic instantiation of `$1` from here"
  364. var info = lastinfo
  365. for i in 0..<conf.m.msgContext.len:
  366. let context = conf.m.msgContext[i]
  367. if context.info != lastinfo and context.info != info:
  368. if conf.structuredErrorHook != nil:
  369. conf.structuredErrorHook(conf, context.info, instantiationFrom,
  370. Severity.Hint)
  371. else:
  372. let message = if context.detail == "":
  373. instantiationFrom
  374. else:
  375. instantiationOfFrom.format(context.detail)
  376. styledMsgWriteln(styleBright, conf.toFileLineCol(context.info), " ", resetStyle, message)
  377. info = context.info
  378. proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
  379. msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
  380. proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
  381. conf.m.fileInfos[fileIdx.int32].lines.add line
  382. proc numLines*(conf: ConfigRef, fileIdx: FileIndex): int =
  383. ## xxx there's an off by 1 error that should be fixed; if a file ends with "foo" or "foo\n"
  384. ## it will return same number of lines (ie, a trailing empty line is discounted)
  385. result = conf.m.fileInfos[fileIdx.int32].lines.len
  386. if result == 0:
  387. try:
  388. for line in lines(toFullPathConsiderDirty(conf, fileIdx).string):
  389. addSourceLine conf, fileIdx, line.string
  390. except IOError:
  391. discard
  392. result = conf.m.fileInfos[fileIdx.int32].lines.len
  393. proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
  394. ## 1-based index (matches editor line numbers); 1st line is for i.line = 1
  395. ## last valid line is `numLines` inclusive
  396. if i.fileIndex.int32 < 0: return ""
  397. let num = numLines(conf, i.fileIndex)
  398. # can happen if the error points to EOF:
  399. if i.line.int > num: return ""
  400. result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
  401. proc writeSurroundingSrc(conf: ConfigRef; info: TLineInfo) =
  402. const indent = " "
  403. msgWriteln(conf, indent & $sourceLine(conf, info))
  404. if info.col >= 0:
  405. msgWriteln(conf, indent & spaces(info.col) & '^')
  406. proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): string =
  407. let title = case msg
  408. of warnMin..warnMax: WarningTitle
  409. of hintMin..hintMax: HintTitle
  410. else: ErrorTitle
  411. conf.toFileLineCol(info) & " " & title & getMessageStr(msg, arg)
  412. proc liMessage*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
  413. eh: TErrorHandling, info2: InstantiationInfo, isRaw = false) {.noinline.} =
  414. var
  415. title: string
  416. color: ForegroundColor
  417. ignoreMsg = false
  418. sev: Severity
  419. let kind = if msg != hintUserRaw: msg.msgToStr else: "" # xxx not sure why hintUserRaw is special
  420. case msg
  421. of errMin..errMax:
  422. sev = Severity.Error
  423. writeContext(conf, info)
  424. title = ErrorTitle
  425. color = ErrorColor
  426. when false:
  427. # we try to filter error messages so that not two error message
  428. # in the same file and line are produced:
  429. # xxx `lastError` is only used in this disabled code; but could be useful to revive
  430. ignoreMsg = conf.m.lastError == info and info != unknownLineInfo and eh != doAbort
  431. if info != unknownLineInfo: conf.m.lastError = info
  432. of warnMin..warnMax:
  433. sev = Severity.Warning
  434. ignoreMsg = not conf.hasWarn(msg)
  435. if msg in conf.warningAsErrors:
  436. ignoreMsg = false
  437. title = ErrorTitle
  438. else:
  439. title = WarningTitle
  440. if not ignoreMsg: writeContext(conf, info)
  441. color = WarningColor
  442. inc(conf.warnCounter)
  443. of hintMin..hintMax:
  444. sev = Severity.Hint
  445. ignoreMsg = not conf.hasHint(msg)
  446. title = HintTitle
  447. color = HintColor
  448. inc(conf.hintCounter)
  449. let s = if isRaw: arg else: getMessageStr(msg, arg)
  450. if not ignoreMsg:
  451. let loc = if info != unknownLineInfo: conf.toFileLineCol(info) & " " else: ""
  452. var kindmsg = if kind.len > 0: KindFormat % kind else: ""
  453. if conf.structuredErrorHook != nil:
  454. conf.structuredErrorHook(conf, info, s & kindmsg, sev)
  455. if not ignoreMsgBecauseOfIdeTools(conf, msg):
  456. if msg == hintProcessing:
  457. msgWrite(conf, ".")
  458. else:
  459. styledMsgWriteln(styleBright, loc, resetStyle, color, title, resetStyle, s, KindColor, kindmsg)
  460. if conf.hasHint(hintSource) and info != unknownLineInfo:
  461. conf.writeSurroundingSrc(info)
  462. if hintMsgOrigin in conf.mainPackageNotes:
  463. styledMsgWriteln(styleBright, toFileLineCol(info2), resetStyle,
  464. " compiler msg initiated here", KindColor,
  465. KindFormat % hintMsgOrigin.msgToStr,
  466. resetStyle)
  467. handleError(conf, msg, eh, s)
  468. template rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
  469. let arg = msgKindToString(msg) % args
  470. liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc(), isRaw = true)
  471. template rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
  472. liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc())
  473. template fatal*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  474. # this fixes bug #7080 so that it is at least obvious 'fatal' was executed.
  475. conf.m.errorOutputs = {eStdOut, eStdErr}
  476. liMessage(conf, info, msg, arg, doAbort, instLoc())
  477. template globalAssert*(conf: ConfigRef; cond: untyped, info: TLineInfo = unknownLineInfo, arg = "") =
  478. ## avoids boilerplate
  479. if not cond:
  480. var arg2 = "'$1' failed" % [astToStr(cond)]
  481. if arg.len > 0: arg2.add "; " & astToStr(arg) & ": " & arg
  482. liMessage(conf, info, errGenerated, arg2, doRaise, instLoc())
  483. template globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  484. ## `local` means compilation keeps going until errorMax is reached (via `doNothing`),
  485. ## `global` means it stops.
  486. liMessage(conf, info, msg, arg, doRaise, instLoc())
  487. template globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
  488. liMessage(conf, info, errGenerated, arg, doRaise, instLoc())
  489. template localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  490. liMessage(conf, info, msg, arg, doNothing, instLoc())
  491. template localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
  492. liMessage(conf, info, errGenerated, arg, doNothing, instLoc())
  493. template localError*(conf: ConfigRef; info: TLineInfo, format: string, params: openArray[string]) =
  494. liMessage(conf, info, errGenerated, format % params, doNothing, instLoc())
  495. template message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
  496. liMessage(conf, info, msg, arg, doNothing, instLoc())
  497. proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
  498. if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
  499. writeContext(conf, info)
  500. liMessage(conf, info, errInternal, errMsg, doAbort, info2)
  501. template internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
  502. internalErrorImpl(conf, info, errMsg, instLoc())
  503. template internalError*(conf: ConfigRef; errMsg: string) =
  504. internalErrorImpl(conf, unknownLineInfo, errMsg, instLoc())
  505. template internalAssert*(conf: ConfigRef, e: bool) =
  506. # xxx merge with `globalAssert`
  507. if not e:
  508. const info2 = instLoc()
  509. let arg = info2.toFileLineCol
  510. internalErrorImpl(conf, unknownLineInfo, arg, info2)
  511. template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string) =
  512. let m = "'$2' should be: '$1'" % [beau, got]
  513. let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
  514. liMessage(conf, info, msg, m, doNothing, instLoc())
  515. proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope =
  516. if i.fileIndex.int32 < 0:
  517. result = makeCString "???"
  518. elif optExcessiveStackTrace in conf.globalOptions:
  519. result = conf.m.fileInfos[i.fileIndex.int32].quotedFullName
  520. else:
  521. result = conf.m.fileInfos[i.fileIndex.int32].quotedName
  522. template listMsg(title, r) =
  523. msgWriteln(conf, title)
  524. for a in r: msgWriteln(conf, " [$1] $2" % [if a in conf.notes: "x" else: " ", a.msgToStr])
  525. proc listWarnings*(conf: ConfigRef) = listMsg("Warnings:", warnMin..warnMax)
  526. proc listHints*(conf: ConfigRef) = listMsg("Hints:", hintMin..hintMax)