Files
Nim/compiler/msgs.nim
Danil Yarantsev 5702a60793 Change severity of template instantiation message [backport] (#14526)
* Change severity of template instatiation msg

* Add a test

* Trailing newline argh

(cherry picked from commit 3698f197f1)
2020-06-01 17:41:04 +02:00

602 lines
20 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
options, strutils, os, tables, ropes, terminal, macros,
lineinfos, pathutils
proc toCChar*(c: char; result: var string) =
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 =
const MaxLineLength = 64
result = nil
var res = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
res.add("\"")
for i in 0..<s.len:
if (i + 1) mod MaxLineLength == 0:
res.add("\"\L\"")
toCChar(s[i], res)
res.add('\"')
result.add(rope(res))
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 fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
var
canon: AbsoluteFile
try:
canon = canonicalizePath(conf, filename)
except OSError:
canon = filename
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)
shallow(canon.string)
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
if conf.m.filenameToIndexTbl.hasKey(canon.string):
isKnownFile = true
result = conf.m.filenameToIndexTbl[canon.string]
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[canon.string] = result
proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
var dummy: bool
result = fileInfoIdx(conf, filename, 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)
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
PosFormat = "$1($2, $3) "
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
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
const
commandLineDesc = "command line"
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
shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
assert fileIdx.int32 >= 0
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 toMsgFilename*(conf: ConfigRef; info: FileIndex): string =
let
absPath = toFullPath(conf, info)
relPath = toProjPath(conf, info)
result = if (optListFullPaths in conf.globalOptions) or
(relPath.len > absPath.len) or
(relPath.count("..") > 2):
absPath
else:
relPath
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*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
# consider calling `helpers.lineInfoToString` instead
result = toMsgFilename(conf, info) & "(" & $info.line & ", " &
$(info.col + 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
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
if not isNil(conf.writelnHook) and msgSkipHook notin flags:
conf.writelnHook(s)
elif optStdout in conf.globalOptions or msgStdout in flags:
if eStdOut in conf.m.errorOutputs:
writeLine(stdout, s)
flushFile(stdout)
else:
if eStdErr in conf.m.errorOutputs:
writeLine(stderr, s)
# 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)
template callWritelnHook(args: varargs[string, `$`]) =
conf.writelnHook concat(args)
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:
callIgnoringStyle(writeLine, stdout, args)
flushFile(stdout)
else:
if eStdErr in conf.m.errorOutputs:
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 coordToStr(coord: int): string =
if coord == -1: result = "???"
else: result = $coord
proc msgKindToString*(kind: TMsgKind): string =
# later versions may provide translated error messages
result = MsgKindToStr[kind]
proc getMessageStr(msg: TMsgKind, arg: string): string =
result = 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 defined(debug) or msg == errInternal or conf.hasHint(hintStackTrace):
{.gcsafe.}:
if stackTraceAvailable() and isNil(conf.writelnHook):
writeStackTrace()
else:
styledMsgWriteln(fgRed, "No stack traceback available\n" &
"To create a stacktrace, rerun compilation with ./koch temp " &
conf.command & " <file>")
quit 1
proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string) =
if msg >= fatalMin and msg <= fatalMax:
if conf.cmd == cmdIdeTools: log(s)
quit(conf, msg)
if msg >= errMin and msg <= errMax:
inc(conf.errorCounter)
conf.exitcode = 1'i8
if conf.errorCounter >= conf.errorMax:
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,
PosFormat % [toMsgFilename(conf, context.info),
coordToStr(context.info.line.int),
coordToStr(context.info.col+ColOffset)],
resetStyle,
message)
info = context.info
proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
proc rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
var
title: string
color: ForegroundColor
kind: string
sev: Severity
case msg
of errMin..errMax:
sev = Severity.Error
writeContext(conf, unknownLineInfo)
title = ErrorTitle
color = ErrorColor
of warnMin..warnMax:
sev = Severity.Warning
if not conf.hasWarn(msg): return
writeContext(conf, unknownLineInfo)
title = WarningTitle
color = WarningColor
kind = WarningsToStr[ord(msg) - ord(warnMin)]
inc(conf.warnCounter)
of hintMin..hintMax:
sev = Severity.Hint
if not conf.hasHint(msg): return
title = HintTitle
color = HintColor
if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
inc(conf.hintCounter)
let s = msgKindToString(msg) % args
if conf.structuredErrorHook != nil:
conf.structuredErrorHook(conf, unknownLineInfo,
s & (if kind.len > 0: KindFormat % kind else: ""), sev)
if not ignoreMsgBecauseOfIdeTools(conf, msg):
if kind.len > 0:
styledMsgWriteln(color, title, resetStyle, s,
KindColor, `%`(KindFormat, kind))
else:
styledMsgWriteln(color, title, resetStyle, s)
handleError(conf, msg, doAbort, s)
proc rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
rawMessage(conf, msg, [arg])
proc resetAttributes*(conf: ConfigRef) =
if {optUseColors, optStdout} * conf.globalOptions == {optUseColors}:
terminal.resetAttributes(stderr)
proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
conf.m.fileInfos[fileIdx.int32].lines.add line
proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
if i.fileIndex.int32 < 0: return ""
if conf.m.fileInfos[i.fileIndex.int32].lines.len == 0:
try:
for line in lines(toFullPathConsiderDirty(conf, i)):
addSourceLine conf, i.fileIndex, line.string
except IOError:
discard
assert i.fileIndex.int32 < conf.m.fileInfos.len
# can happen if the error points to EOF:
if i.line.int > conf.m.fileInfos[i.fileIndex.int32].lines.len: return ""
result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
proc writeSurroundingSrc(conf: ConfigRef; info: TLineInfo) =
const indent = " "
msgWriteln(conf, indent & $sourceLine(conf, info))
if info.col >= 0:
msgWriteln(conf, 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
result = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int),
coordToStr(info.col+ColOffset)] &
title &
getMessageStr(msg, arg)
proc liMessage(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
eh: TErrorHandling) =
var
title: string
color: ForegroundColor
kind: string
ignoreMsg = false
sev: Severity
case msg
of errMin..errMax:
sev = Severity.Error
writeContext(conf, info)
title = ErrorTitle
color = ErrorColor
# we try to filter error messages so that not two error message
# in the same file and line are produced:
#ignoreMsg = lastError == info and eh != doAbort
conf.m.lastError = info
of warnMin..warnMax:
sev = Severity.Warning
ignoreMsg = not conf.hasWarn(msg)
if not ignoreMsg: writeContext(conf, info)
title = WarningTitle
color = WarningColor
kind = WarningsToStr[ord(msg) - ord(warnMin)]
inc(conf.warnCounter)
of hintMin..hintMax:
sev = Severity.Hint
ignoreMsg = not conf.hasHint(msg)
title = HintTitle
color = HintColor
if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
inc(conf.hintCounter)
let x = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int),
coordToStr(info.col+ColOffset)]
let s = getMessageStr(msg, arg)
if not ignoreMsg:
if conf.structuredErrorHook != nil:
conf.structuredErrorHook(conf, info, s & (if kind.len > 0: KindFormat % kind else: ""), sev)
if not ignoreMsgBecauseOfIdeTools(conf, msg):
if kind.len > 0:
styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s,
KindColor, `%`(KindFormat, kind))
else:
styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s)
if conf.hasHint(hintSource):
conf.writeSurroundingSrc(info)
handleError(conf, msg, eh, s)
proc fatal*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
# this fixes bug #7080 so that it is at least obvious 'fatal'
# was executed.
conf.m.errorOutputs = {eStdOut, eStdErr}
liMessage(conf, info, msg, arg, doAbort)
proc globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
liMessage(conf, info, msg, arg, doRaise)
proc globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
liMessage(conf, info, errGenerated, arg, doRaise)
proc localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
liMessage(conf, info, msg, arg, doNothing)
proc localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
liMessage(conf, info, errGenerated, arg, doNothing)
proc localError*(conf: ConfigRef; info: TLineInfo, format: string, params: openArray[string]) =
localError(conf, info, format % params)
proc message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
liMessage(conf, info, msg, arg, doNothing)
proc internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
writeContext(conf, info)
liMessage(conf, info, errInternal, errMsg, doAbort)
proc internalError*(conf: ConfigRef; errMsg: string) =
if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
writeContext(conf, unknownLineInfo)
rawMessage(conf, errInternal, errMsg)
template assertNotNil*(conf: ConfigRef; e): untyped =
if e == nil: internalError(conf, $instantiationInfo())
e
template internalAssert*(conf: ConfigRef, e: bool) =
if not e: internalError(conf, $instantiationInfo())
proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope =
if i.fileIndex.int32 < 0:
result = makeCString "???"
elif optExcessiveStackTrace in conf.globalOptions:
result = conf.m.fileInfos[i.fileIndex.int32].quotedFullName
else:
result = conf.m.fileInfos[i.fileIndex.int32].quotedName
proc listWarnings*(conf: ConfigRef) =
msgWriteln(conf, "Warnings:")
for warn in warnMin..warnMax:
msgWriteln(conf, " [$1] $2" % [
if warn in conf.notes: "x" else: " ",
lineinfos.WarningsToStr[ord(warn) - ord(warnMin)]
])
proc listHints*(conf: ConfigRef) =
msgWriteln(conf, "Hints:")
for hint in hintMin..hintMax:
msgWriteln(conf, " [$1] $2" % [
if hint in conf.notes: "x" else: " ",
lineinfos.HintsToStr[ord(hint) - ord(hintMin)]
])
proc lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string) =
let m = "'$2' should be: '$1'" % [beau, got]
if optStyleError in conf.globalOptions:
localError(conf, info, m)
else:
message(conf, info, hintName, m)