mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-29 17:34:43 +00:00
Previously, in certain cases, the compiler would generate debug info for
the correct line number, but for the wrong .nim source file.
(cherry picked from commit 7e3bac9235)
745 lines
28 KiB
Nim
745 lines
28 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2013 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
import
|
|
std/[strutils, os, tables, terminal, macros, times],
|
|
std/private/miscdollars,
|
|
options, lineinfos, pathutils
|
|
|
|
import ropes except `%`
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/[syncio, assertions]
|
|
|
|
|
|
type InstantiationInfo* = typeof(instantiationInfo())
|
|
template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true)
|
|
|
|
template toStdOrrKind(stdOrr): untyped =
|
|
if stdOrr == stdout: stdOrrStdout else: stdOrrStderr
|
|
|
|
proc toLowerAscii(a: var string) {.inline.} =
|
|
for c in mitems(a):
|
|
if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8)
|
|
|
|
proc flushDot*(conf: ConfigRef) =
|
|
## safe to call multiple times
|
|
# xxx one edge case not yet handled is when `printf` is called at CT with `compiletimeFFI`.
|
|
let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr
|
|
let stdOrrKind = toStdOrrKind(stdOrr)
|
|
if stdOrrKind in conf.lastMsgWasDot:
|
|
conf.lastMsgWasDot.excl stdOrrKind
|
|
write(stdOrr, "\n")
|
|
|
|
proc toCChar*(c: char; result: var string) {.inline.} =
|
|
case c
|
|
of '\0'..'\x1F', '\x7F'..'\xFF':
|
|
result.add '\\'
|
|
result.add toOctal(c)
|
|
of '\'', '\"', '\\', '?':
|
|
result.add '\\'
|
|
result.add c
|
|
else:
|
|
result.add c
|
|
|
|
proc makeCString*(s: string): Rope =
|
|
result = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
|
|
result.add("\"")
|
|
for i in 0..<s.len:
|
|
# line wrapping of string litterals in cgen'd code was a bad idea, e.g. causes: bug #16265
|
|
# It also makes reading c sources or grepping harder, for zero benefit.
|
|
# const MaxLineLength = 64
|
|
# if (i + 1) mod MaxLineLength == 0:
|
|
# res.add("\"\L\"")
|
|
toCChar(s[i], result)
|
|
result.add('\"')
|
|
|
|
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
|
|
result.fullPath = fullPath
|
|
#shallow(result.fullPath)
|
|
result.projPath = projPath
|
|
#shallow(result.projPath)
|
|
result.shortName = fullPath.extractFilename
|
|
result.quotedName = result.shortName.makeCString
|
|
result.quotedFullName = fullPath.string.makeCString
|
|
result.lines = @[]
|
|
when defined(nimpretty):
|
|
if not result.fullPath.isEmpty:
|
|
try:
|
|
result.fullContent = readFile(result.fullPath.string)
|
|
except IOError:
|
|
#rawMessage(errCannotOpenFile, result.fullPath)
|
|
# XXX fixme
|
|
result.fullContent = ""
|
|
|
|
when defined(nimpretty):
|
|
proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string =
|
|
substr(conf.m.fileInfos[fid.int].fullContent, a, b)
|
|
|
|
proc canonicalCase(path: var string) {.inline.} =
|
|
## the idea is to only use this for checking whether a path is already in
|
|
## the table but otherwise keep the original case
|
|
when FileSystemCaseSensitive: discard
|
|
else: toLowerAscii(path)
|
|
|
|
proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
|
|
var
|
|
canon: AbsoluteFile
|
|
try:
|
|
canon = canonicalizePath(conf, filename)
|
|
except OSError:
|
|
canon = filename
|
|
canon.string.canonicalCase
|
|
result = conf.m.filenameToIndexTbl.hasKey(canon.string)
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex =
|
|
var
|
|
canon: AbsoluteFile
|
|
pseudoPath = false
|
|
|
|
try:
|
|
canon = canonicalizePath(conf, filename)
|
|
except OSError:
|
|
canon = filename
|
|
# The compiler uses "filenames" such as `command line` or `stdin`
|
|
# This flag indicates that we are working with such a path here
|
|
pseudoPath = true
|
|
|
|
var canon2 = canon.string
|
|
canon2.canonicalCase
|
|
|
|
if conf.m.filenameToIndexTbl.hasKey(canon2):
|
|
isKnownFile = true
|
|
result = conf.m.filenameToIndexTbl[canon2]
|
|
else:
|
|
isKnownFile = false
|
|
result = conf.m.fileInfos.len.FileIndex
|
|
conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename
|
|
else: relativeTo(canon, conf.projectPath)))
|
|
conf.m.filenameToIndexTbl[canon2] = result
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
|
|
var dummy: bool
|
|
result = fileInfoIdx(conf, filename, dummy)
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile; isKnownFile: var bool): FileIndex =
|
|
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), isKnownFile)
|
|
|
|
proc fileInfoIdx*(conf: ConfigRef; filename: RelativeFile): FileIndex =
|
|
var dummy: bool
|
|
fileInfoIdx(conf, AbsoluteFile expandFilename(filename.string), dummy)
|
|
|
|
proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
|
|
result.fileIndex = fileInfoIdx
|
|
if line < int high(uint16):
|
|
result.line = uint16(line)
|
|
else:
|
|
result.line = high(uint16)
|
|
if col < int high(int16):
|
|
result.col = int16(col)
|
|
else:
|
|
result.col = -1
|
|
|
|
proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} =
|
|
result = newLineInfo(fileInfoIdx(conf, filename), line, col)
|
|
|
|
const gCmdLineInfo* = newLineInfo(commandLineIdx, 1, 1)
|
|
|
|
proc concat(strings: openArray[string]): string =
|
|
var totalLen = 0
|
|
for s in strings: totalLen += s.len
|
|
result = newStringOfCap totalLen
|
|
for s in strings: result.add s
|
|
|
|
proc suggestWriteln*(conf: ConfigRef; s: string) =
|
|
if eStdOut in conf.m.errorOutputs:
|
|
if isNil(conf.writelnHook):
|
|
writeLine(stdout, s)
|
|
flushFile(stdout)
|
|
else:
|
|
conf.writelnHook(s)
|
|
|
|
proc msgQuit*(x: int8) = quit x
|
|
proc msgQuit*(x: string) = quit x
|
|
|
|
proc suggestQuit*() =
|
|
raise newException(ESuggestDone, "suggest done")
|
|
|
|
# this format is understood by many text editors: it is the same that
|
|
# Borland and Freepascal use
|
|
const
|
|
KindFormat = " [$1]"
|
|
KindColor = fgCyan
|
|
ErrorTitle = "Error: "
|
|
ErrorColor = fgRed
|
|
WarningTitle = "Warning: "
|
|
WarningColor = fgYellow
|
|
HintTitle = "Hint: "
|
|
HintColor = fgGreen
|
|
# NOTE: currently line info line numbers start with 1,
|
|
# but column numbers start with 0, however most editors expect
|
|
# first column to be 1, so we need to +1 here
|
|
ColOffset* = 1
|
|
commandLineDesc* = "command line"
|
|
|
|
proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len
|
|
proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L)
|
|
|
|
proc pushInfoContext*(conf: ConfigRef; info: TLineInfo; detail: string = "") =
|
|
conf.m.msgContext.add((info, detail))
|
|
|
|
proc popInfoContext*(conf: ConfigRef) =
|
|
setLen(conf.m.msgContext, conf.m.msgContext.len - 1)
|
|
|
|
proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
|
|
let i = if index < 0: conf.m.msgContext.len + index else: index
|
|
if i >=% conf.m.msgContext.len: result = unknownLineInfo
|
|
else: result = conf.m.msgContext[i].info
|
|
|
|
template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
if fileIdx.int32 < 0 or conf == nil:
|
|
(if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
else:
|
|
conf.m.fileInfos[fileIdx.int32].shortName
|
|
|
|
proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
if fileIdx.int32 < 0 or conf == nil:
|
|
(if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
else: conf.m.fileInfos[fileIdx.int32].projPath.string
|
|
|
|
proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
if fileIdx.int32 < 0 or conf == nil:
|
|
result = (if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
else:
|
|
result = conf.m.fileInfos[fileIdx.int32].fullPath.string
|
|
|
|
proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
|
|
assert fileIdx.int32 >= 0
|
|
conf.m.fileInfos[fileIdx.int32].dirtyFile = filename
|
|
setLen conf.m.fileInfos[fileIdx.int32].lines, 0
|
|
|
|
proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
|
|
assert fileIdx.int32 >= 0
|
|
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
|
conf.m.fileInfos[fileIdx.int32].hash = hash
|
|
else:
|
|
shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
|
|
|
|
|
|
proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
assert fileIdx.int32 >= 0
|
|
when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
|
|
result = conf.m.fileInfos[fileIdx.int32].hash
|
|
else:
|
|
shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
|
|
|
|
proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
|
|
if fileIdx.int32 < 0:
|
|
result = AbsoluteFile(if fileIdx == commandLineIdx: commandLineDesc else: "???")
|
|
elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
|
|
result = conf.m.fileInfos[fileIdx.int32].dirtyFile
|
|
else:
|
|
result = conf.m.fileInfos[fileIdx.int32].fullPath
|
|
|
|
template toFilename*(conf: ConfigRef; info: TLineInfo): string =
|
|
toFilename(conf, info.fileIndex)
|
|
|
|
template toProjPath*(conf: ConfigRef; info: TLineInfo): string =
|
|
toProjPath(conf, info.fileIndex)
|
|
|
|
template toFullPath*(conf: ConfigRef; info: TLineInfo): string =
|
|
toFullPath(conf, info.fileIndex)
|
|
|
|
template toFullPathConsiderDirty*(conf: ConfigRef; info: TLineInfo): string =
|
|
string toFullPathConsiderDirty(conf, info.fileIndex)
|
|
|
|
proc toFilenameOption*(conf: ConfigRef, fileIdx: FileIndex, opt: FilenameOption): string =
|
|
case opt
|
|
of foAbs: result = toFullPath(conf, fileIdx)
|
|
of foRelProject: result = toProjPath(conf, fileIdx)
|
|
of foCanonical:
|
|
let absPath = toFullPath(conf, fileIdx)
|
|
result = canonicalImportAux(conf, absPath.AbsoluteFile)
|
|
of foName: result = toProjPath(conf, fileIdx).lastPathPart
|
|
of foLegacyRelProj:
|
|
let
|
|
absPath = toFullPath(conf, fileIdx)
|
|
relPath = toProjPath(conf, fileIdx)
|
|
result = if (relPath.len > absPath.len) or (relPath.count("..") > 2):
|
|
absPath
|
|
else:
|
|
relPath
|
|
of foStacktrace:
|
|
if optExcessiveStackTrace in conf.globalOptions:
|
|
result = toFilenameOption(conf, fileIdx, foAbs)
|
|
else:
|
|
result = toFilenameOption(conf, fileIdx, foName)
|
|
|
|
proc toMsgFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
|
|
toFilenameOption(conf, fileIdx, conf.filenameOption)
|
|
|
|
template toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
|
|
toMsgFilename(conf, info.fileIndex)
|
|
|
|
proc toLinenumber*(info: TLineInfo): int {.inline.} =
|
|
result = int info.line
|
|
|
|
proc toColumn*(info: TLineInfo): int {.inline.} =
|
|
result = info.col
|
|
|
|
proc toFileLineCol(info: InstantiationInfo): string {.inline.} =
|
|
result.toLocation(info.filename, info.line, info.column + ColOffset)
|
|
|
|
proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
|
|
result.toLocation(toMsgFilename(conf, info), info.line.int, info.col.int + ColOffset)
|
|
|
|
proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info)
|
|
|
|
proc `$`*(info: TLineInfo): string {.error.} = discard
|
|
|
|
proc `??`* (conf: ConfigRef; info: TLineInfo, filename: string): bool =
|
|
# only for debugging purposes
|
|
result = filename in toFilename(conf, info)
|
|
|
|
type
|
|
MsgFlag* = enum ## flags altering msgWriteln behavior
|
|
msgStdout, ## force writing to stdout, even stderr is default
|
|
msgSkipHook ## skip message hook even if it is present
|
|
msgNoUnitSep ## the message is a complete "paragraph".
|
|
MsgFlags* = set[MsgFlag]
|
|
|
|
proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
|
|
## Writes given message string to stderr by default.
|
|
## If ``--stdout`` option is given, writes to stdout instead. If message hook
|
|
## is present, then it is used to output message rather than stderr/stdout.
|
|
## This behavior can be altered by given optional flags.
|
|
|
|
## This is used for 'nim dump' etc. where we don't have nimsuggest
|
|
## support.
|
|
#if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
|
|
let sep = if msgNoUnitSep notin flags: conf.unitSep else: ""
|
|
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
|
|
conf.writelnHook(s & sep)
|
|
elif optStdout in conf.globalOptions or msgStdout in flags:
|
|
if eStdOut in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
write stdout, s
|
|
writeLine(stdout, sep)
|
|
flushFile(stdout)
|
|
else:
|
|
if eStdErr in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
write stderr, s
|
|
writeLine(stderr, sep)
|
|
# On Windows stderr is fully-buffered when piped, regardless of C std.
|
|
when defined(windows):
|
|
flushFile(stderr)
|
|
|
|
macro callIgnoringStyle(theProc: typed, first: typed,
|
|
args: varargs[typed]): untyped =
|
|
let typForegroundColor = bindSym"ForegroundColor".getType
|
|
let typBackgroundColor = bindSym"BackgroundColor".getType
|
|
let typStyle = bindSym"Style".getType
|
|
let typTerminalCmd = bindSym"TerminalCmd".getType
|
|
result = newCall(theProc)
|
|
if first.kind != nnkNilLit: result.add(first)
|
|
for arg in children(args[0][1]):
|
|
if arg.kind == nnkNilLit: continue
|
|
let typ = arg.getType
|
|
if typ.kind != nnkEnumTy or
|
|
typ != typForegroundColor and
|
|
typ != typBackgroundColor and
|
|
typ != typStyle and
|
|
typ != typTerminalCmd:
|
|
result.add(arg)
|
|
|
|
macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
|
|
result = newCall(bindSym"styledWriteLine")
|
|
result.add(bindSym"stderr")
|
|
for arg in children(args[0][1]):
|
|
result.add(arg)
|
|
when false:
|
|
# not needed because styledWriteLine already ends with resetAttributes
|
|
result = newStmtList(result, newCall(bindSym"resetAttributes", bindSym"stderr"))
|
|
|
|
template callWritelnHook(args: varargs[string, `$`]) =
|
|
conf.writelnHook concat(args)
|
|
|
|
proc msgWrite(conf: ConfigRef; s: string) =
|
|
if conf.m.errorOutputs != {}:
|
|
let stdOrr =
|
|
if optStdout in conf.globalOptions:
|
|
stdout
|
|
else:
|
|
stderr
|
|
write(stdOrr, s)
|
|
flushFile(stdOrr)
|
|
conf.lastMsgWasDot.incl stdOrr.toStdOrrKind() # subsequent writes need `flushDot`
|
|
|
|
template styledMsgWriteln(args: varargs[typed]) =
|
|
if not isNil(conf.writelnHook):
|
|
callIgnoringStyle(callWritelnHook, nil, args)
|
|
elif optStdout in conf.globalOptions:
|
|
if eStdOut in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
callIgnoringStyle(writeLine, stdout, args)
|
|
flushFile(stdout)
|
|
elif eStdErr in conf.m.errorOutputs:
|
|
flushDot(conf)
|
|
if optUseColors in conf.globalOptions:
|
|
callStyledWriteLineStderr(args)
|
|
else:
|
|
callIgnoringStyle(writeLine, stderr, args)
|
|
# On Windows stderr is fully-buffered when piped, regardless of C std.
|
|
when defined(windows):
|
|
flushFile(stderr)
|
|
|
|
proc msgKindToString*(kind: TMsgKind): string = MsgKindToStr[kind]
|
|
# later versions may provide translated error messages
|
|
|
|
proc getMessageStr(msg: TMsgKind, arg: string): string = msgKindToString(msg) % [arg]
|
|
|
|
type TErrorHandling* = enum doNothing, doAbort, doRaise
|
|
|
|
proc log*(s: string) =
|
|
var f: File
|
|
if open(f, getHomeDir() / "nimsuggest.log", fmAppend):
|
|
f.writeLine(s)
|
|
close(f)
|
|
|
|
proc quit(conf: ConfigRef; msg: TMsgKind) {.gcsafe.} =
|
|
if conf.isDefined("nimDebug"): quitOrRaise(conf, $msg)
|
|
elif defined(debug) or msg == errInternal or conf.hasHint(hintStackTrace):
|
|
{.gcsafe.}:
|
|
if stackTraceAvailable() and isNil(conf.writelnHook):
|
|
writeStackTrace()
|
|
else:
|
|
styledMsgWriteln(fgRed, """
|
|
No stack traceback available
|
|
To create a stacktrace, rerun compilation with './koch temp $1 <file>', see $2 for details""" %
|
|
[conf.command, "intern.html#debugging-the-compiler".createDocLink], conf.unitSep)
|
|
quit 1
|
|
|
|
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string, ignoreMsg: bool) =
|
|
if msg in fatalMsgs:
|
|
if conf.cmd == cmdIdeTools: log(s)
|
|
if conf.cmd != cmdIdeTools or msg != errFatal:
|
|
quit(conf, msg)
|
|
if msg >= errMin and msg <= errMax or
|
|
(msg in warnMin..hintMax and msg in conf.warningAsErrors and not ignoreMsg):
|
|
inc(conf.errorCounter)
|
|
conf.exitcode = 1'i8
|
|
if conf.errorCounter >= conf.errorMax:
|
|
# only really quit when we're not in the new 'nim check --def' mode:
|
|
if conf.ideCmd == ideNone:
|
|
when defined(nimsuggest):
|
|
#we need to inform the user that something went wrong when initializing NimSuggest
|
|
raiseRecoverableError(s)
|
|
else:
|
|
quit(conf, msg)
|
|
elif eh == doAbort and conf.cmd != cmdIdeTools:
|
|
quit(conf, msg)
|
|
elif eh == doRaise:
|
|
raiseRecoverableError(s)
|
|
|
|
proc `==`*(a, b: TLineInfo): bool =
|
|
result = a.line == b.line and a.fileIndex == b.fileIndex
|
|
|
|
proc exactEquals*(a, b: TLineInfo): bool =
|
|
result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col
|
|
|
|
proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
|
|
const instantiationFrom = "template/generic instantiation from here"
|
|
const instantiationOfFrom = "template/generic instantiation of `$1` from here"
|
|
var info = lastinfo
|
|
for i in 0..<conf.m.msgContext.len:
|
|
let context = conf.m.msgContext[i]
|
|
if context.info != lastinfo and context.info != info:
|
|
if conf.structuredErrorHook != nil:
|
|
conf.structuredErrorHook(conf, context.info, instantiationFrom,
|
|
Severity.Hint)
|
|
else:
|
|
let message =
|
|
if context.detail == "":
|
|
instantiationFrom
|
|
else:
|
|
instantiationOfFrom.format(context.detail)
|
|
styledMsgWriteln(styleBright, conf.toFileLineCol(context.info), " ", resetStyle, message)
|
|
info = context.info
|
|
|
|
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
|
|
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
|
|
|
|
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
|
|
conf.m.fileInfos[fileIdx.int32].lines.add line
|
|
|
|
proc numLines*(conf: ConfigRef, fileIdx: FileIndex): int =
|
|
## xxx there's an off by 1 error that should be fixed; if a file ends with "foo" or "foo\n"
|
|
## it will return same number of lines (ie, a trailing empty line is discounted)
|
|
result = conf.m.fileInfos[fileIdx.int32].lines.len
|
|
if result == 0:
|
|
try:
|
|
for line in lines(toFullPathConsiderDirty(conf, fileIdx).string):
|
|
addSourceLine conf, fileIdx, line
|
|
except IOError:
|
|
discard
|
|
result = conf.m.fileInfos[fileIdx.int32].lines.len
|
|
|
|
proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
|
|
## 1-based index (matches editor line numbers); 1st line is for i.line = 1
|
|
## last valid line is `numLines` inclusive
|
|
if i.fileIndex.int32 < 0: return ""
|
|
let num = numLines(conf, i.fileIndex)
|
|
# can happen if the error points to EOF:
|
|
if i.line.int > num: return ""
|
|
|
|
result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
|
|
|
|
proc getSurroundingSrc(conf: ConfigRef; info: TLineInfo): string =
|
|
if conf.hasHint(hintSource) and info != unknownLineInfo:
|
|
const indent = " "
|
|
result = "\n" & indent & $sourceLine(conf, info)
|
|
if info.col >= 0:
|
|
result.add "\n" & indent & spaces(info.col) & '^'
|
|
|
|
proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): string =
|
|
let title = case msg
|
|
of warnMin..warnMax: WarningTitle
|
|
of hintMin..hintMax: HintTitle
|
|
else: ErrorTitle
|
|
conf.toFileLineCol(info) & " " & title & getMessageStr(msg, arg)
|
|
|
|
proc liMessage*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
|
|
eh: TErrorHandling, info2: InstantiationInfo, isRaw = false,
|
|
ignoreError = false) {.gcsafe, noinline.} =
|
|
var
|
|
title: string
|
|
color: ForegroundColor
|
|
ignoreMsg = false
|
|
sev: Severity
|
|
let errorOutputsOld = conf.m.errorOutputs
|
|
if msg in fatalMsgs:
|
|
# don't gag, refs bug #7080, bug #18278; this can happen with `{.fatal.}`
|
|
# or inside a `tryConstExpr`.
|
|
conf.m.errorOutputs = {eStdOut, eStdErr}
|
|
|
|
let kind = if msg in warnMin..hintMax and msg != hintUserRaw: $msg else: "" # xxx not sure why hintUserRaw is special
|
|
case msg
|
|
of errMin..errMax:
|
|
sev = Severity.Error
|
|
writeContext(conf, info)
|
|
title = ErrorTitle
|
|
color = ErrorColor
|
|
when false:
|
|
# we try to filter error messages so that not two error message
|
|
# in the same file and line are produced:
|
|
# xxx `lastError` is only used in this disabled code; but could be useful to revive
|
|
ignoreMsg = conf.m.lastError == info and info != unknownLineInfo and eh != doAbort
|
|
if info != unknownLineInfo: conf.m.lastError = info
|
|
of warnMin..warnMax:
|
|
sev = Severity.Warning
|
|
ignoreMsg = not conf.hasWarn(msg)
|
|
if not ignoreMsg and msg in conf.warningAsErrors:
|
|
title = ErrorTitle
|
|
color = ErrorColor
|
|
else:
|
|
title = WarningTitle
|
|
color = WarningColor
|
|
if not ignoreMsg: writeContext(conf, info)
|
|
inc(conf.warnCounter)
|
|
of hintMin..hintMax:
|
|
sev = Severity.Hint
|
|
ignoreMsg = not conf.hasHint(msg)
|
|
if not ignoreMsg and msg in conf.warningAsErrors:
|
|
title = ErrorTitle
|
|
color = ErrorColor
|
|
else:
|
|
title = HintTitle
|
|
color = HintColor
|
|
inc(conf.hintCounter)
|
|
|
|
let s = if isRaw: arg else: getMessageStr(msg, arg)
|
|
if not ignoreMsg:
|
|
let loc = if info != unknownLineInfo: conf.toFileLineCol(info) & " " else: ""
|
|
# we could also show `conf.cmdInput` here for `projectIsCmd`
|
|
var kindmsg = if kind.len > 0: KindFormat % kind else: ""
|
|
if conf.structuredErrorHook != nil:
|
|
conf.structuredErrorHook(conf, info, s & kindmsg, sev)
|
|
if not ignoreMsgBecauseOfIdeTools(conf, msg):
|
|
if msg == hintProcessing and conf.hintProcessingDots:
|
|
msgWrite(conf, ".")
|
|
else:
|
|
styledMsgWriteln(styleBright, loc, resetStyle, color, title, resetStyle, s, KindColor, kindmsg,
|
|
resetStyle, conf.getSurroundingSrc(info), conf.unitSep)
|
|
if hintMsgOrigin in conf.mainPackageNotes:
|
|
# xxx needs a bit of refactoring to honor `conf.filenameOption`
|
|
styledMsgWriteln(styleBright, toFileLineCol(info2), resetStyle,
|
|
" compiler msg initiated here", KindColor,
|
|
KindFormat % $hintMsgOrigin,
|
|
resetStyle, conf.unitSep)
|
|
if not ignoreError:
|
|
handleError(conf, msg, eh, s, ignoreMsg)
|
|
if msg in fatalMsgs:
|
|
# most likely would have died here but just in case, we restore state
|
|
conf.m.errorOutputs = errorOutputsOld
|
|
|
|
template rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
|
|
let arg = msgKindToString(msg) % args
|
|
liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc(), isRaw = true)
|
|
|
|
template rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
|
|
liMessage(conf, unknownLineInfo, msg, arg, eh = doAbort, instLoc())
|
|
|
|
template fatal*(conf: ConfigRef; info: TLineInfo, arg = "", msg = errFatal) =
|
|
liMessage(conf, info, msg, arg, doAbort, instLoc())
|
|
|
|
template globalAssert*(conf: ConfigRef; cond: untyped, info: TLineInfo = unknownLineInfo, arg = "") =
|
|
## avoids boilerplate
|
|
if not cond:
|
|
var arg2 = "'$1' failed" % [astToStr(cond)]
|
|
if arg.len > 0: arg2.add "; " & astToStr(arg) & ": " & arg
|
|
liMessage(conf, info, errGenerated, arg2, doRaise, instLoc())
|
|
|
|
template globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
|
|
## `local` means compilation keeps going until errorMax is reached (via `doNothing`),
|
|
## `global` means it stops.
|
|
liMessage(conf, info, msg, arg, doRaise, instLoc())
|
|
|
|
template globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
|
|
liMessage(conf, info, errGenerated, arg, doRaise, instLoc())
|
|
|
|
template localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
|
|
liMessage(conf, info, msg, arg, doNothing, instLoc())
|
|
|
|
template localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
|
|
liMessage(conf, info, errGenerated, arg, doNothing, instLoc())
|
|
|
|
template message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
|
|
liMessage(conf, info, msg, arg, doNothing, instLoc())
|
|
|
|
proc warningDeprecated*(conf: ConfigRef, info: TLineInfo = gCmdLineInfo, msg = "") {.inline.} =
|
|
message(conf, info, warnDeprecated, msg)
|
|
|
|
proc internalErrorImpl(conf: ConfigRef; info: TLineInfo, errMsg: string, info2: InstantiationInfo) =
|
|
if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
|
|
writeContext(conf, info)
|
|
liMessage(conf, info, errInternal, errMsg, doAbort, info2)
|
|
|
|
template internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
|
|
internalErrorImpl(conf, info, errMsg, instLoc())
|
|
|
|
template internalError*(conf: ConfigRef; errMsg: string) =
|
|
internalErrorImpl(conf, unknownLineInfo, errMsg, instLoc())
|
|
|
|
template internalAssert*(conf: ConfigRef, e: bool) =
|
|
# xxx merge with `globalAssert`
|
|
if not e:
|
|
const info2 = instLoc()
|
|
let arg = info2.toFileLineCol
|
|
internalErrorImpl(conf, unknownLineInfo, arg, info2)
|
|
|
|
template lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string, extraMsg = "") =
|
|
let m = "'$1' should be: '$2'$3" % [got, beau, extraMsg]
|
|
let msg = if optStyleError in conf.globalOptions: errGenerated else: hintName
|
|
liMessage(conf, info, msg, m, doNothing, instLoc())
|
|
|
|
proc quotedFilename*(conf: ConfigRef; fi: FileIndex): Rope =
|
|
if fi.int32 < 0:
|
|
result = makeCString "???"
|
|
elif optExcessiveStackTrace in conf.globalOptions:
|
|
result = conf.m.fileInfos[fi.int32].quotedFullName
|
|
else:
|
|
result = conf.m.fileInfos[fi.int32].quotedName
|
|
|
|
proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope =
|
|
quotedFilename(conf, i.fileIndex)
|
|
|
|
template listMsg(title, r) =
|
|
msgWriteln(conf, title, {msgNoUnitSep})
|
|
for a in r: msgWriteln(conf, " [$1] $2" % [if a in conf.notes: "x" else: " ", $a], {msgNoUnitSep})
|
|
|
|
proc listWarnings*(conf: ConfigRef) = listMsg("Warnings:", warnMin..warnMax)
|
|
proc listHints*(conf: ConfigRef) = listMsg("Hints:", hintMin..hintMax)
|
|
|
|
proc uniqueModuleName*(conf: ConfigRef; fid: FileIndex): string =
|
|
## The unique module name is guaranteed to only contain {'A'..'Z', 'a'..'z', '0'..'9', '_'}
|
|
## so that it is useful as a C identifier snippet.
|
|
let path = AbsoluteFile toFullPath(conf, fid)
|
|
let rel =
|
|
if path.string.startsWith(conf.libpath.string):
|
|
relativeTo(path, conf.libpath).string
|
|
else:
|
|
relativeTo(path, conf.projectPath).string
|
|
let trunc = if rel.endsWith(".nim"): rel.len - len(".nim") else: rel.len
|
|
result = newStringOfCap(trunc)
|
|
for i in 0..<trunc:
|
|
let c = rel[i]
|
|
case c
|
|
of 'a'..'z':
|
|
result.add c
|
|
of {os.DirSep, os.AltSep}:
|
|
result.add 'Z' # because it looks a bit like '/'
|
|
of '.':
|
|
result.add 'O' # a circle
|
|
else:
|
|
# We mangle upper letters and digits too so that there cannot
|
|
# be clashes with our special meanings of 'Z' and 'O'
|
|
result.addInt ord(c)
|
|
|
|
proc genSuccessX*(conf: ConfigRef) =
|
|
let mem =
|
|
when declared(system.getMaxMem): formatSize(getMaxMem()) & " peakmem"
|
|
else: formatSize(getTotalMem()) & " totmem"
|
|
let loc = $conf.linesCompiled
|
|
var build = ""
|
|
var flags = ""
|
|
const debugModeHints = "none (DEBUG BUILD, `-d:release` generates faster code)"
|
|
if conf.cmd in cmdBackends:
|
|
if conf.backend != backendJs:
|
|
build.add "mm: $#; " % $conf.selectedGC
|
|
if optThreads in conf.globalOptions: build.add "threads: on; "
|
|
build.add "opt: "
|
|
if optOptimizeSpeed in conf.options: build.add "speed"
|
|
elif optOptimizeSize in conf.options: build.add "size"
|
|
else: build.add debugModeHints
|
|
# pending https://github.com/timotheecour/Nim/issues/752, point to optimization.html
|
|
if isDefined(conf, "danger"): flags.add " -d:danger"
|
|
elif isDefined(conf, "release"): flags.add " -d:release"
|
|
else:
|
|
build.add "opt: "
|
|
if isDefined(conf, "danger"):
|
|
build.add "speed"
|
|
flags.add " -d:danger"
|
|
elif isDefined(conf, "release"):
|
|
build.add "speed"
|
|
flags.add " -d:release"
|
|
else: build.add debugModeHints
|
|
if flags.len > 0: build.add "; options:" & flags
|
|
let sec = formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3)
|
|
let project = if conf.filenameOption == foAbs: $conf.projectFull else: $conf.projectName
|
|
# xxx honor conf.filenameOption more accurately
|
|
var output: string
|
|
if optCompileOnly in conf.globalOptions and conf.cmd != cmdJsonscript:
|
|
output = $conf.jsonBuildFile
|
|
elif conf.outFile.isEmpty and conf.cmd notin {cmdJsonscript} + cmdDocLike + cmdBackends:
|
|
# for some cmd we expect a valid absOutFile
|
|
output = "unknownOutput"
|
|
else:
|
|
output = $conf.absOutFile
|
|
if conf.filenameOption != foAbs: output = output.AbsoluteFile.extractFilename
|
|
# xxx honor filenameOption more accurately
|
|
rawMessage(conf, hintSuccessX, [
|
|
"build", build,
|
|
"loc", loc,
|
|
"sec", sec,
|
|
"mem", mem,
|
|
"project", project,
|
|
"output", output,
|
|
])
|