Files
Nim/compiler/msgs.nim
alaviss e259f80fc7 msgs, ccgstmts: fixes #11572 (#11621)
* [refactor] msgs: toFilename now return just the filename

The C codegen uses just the file name for stacktrace when
excessiveStackTrace is off (see quotedName),
so there aren't any reason for other codegen to not do the same.

The file name is now cached in TFileInfo.shortName, which was introduced
for nimsuggest, and went unused after several refactoring of the
compiler.

A toProjPath() proc has been added for the previous behavior of
toFilename().

* ccgstmt: use quotedFilename() for raiseExceptionEx

This is the same proc used for stacktrace when --stacktrace:on

Fixes #11572

* msgs: handle case where file name is not available
2019-06-30 12:30:37 +02:00

591 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, platform, 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)
add(res, "\"")
for i in 0 ..< len(s):
if (i + 1) mod MaxLineLength == 0:
add(res, "\"\L\"")
toCChar(s[i], res)
add(res, '\"')
add(result, 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):
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, len(conf.m.msgContext) - 1)
proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
let L = conf.m.msgContext.len
let i = if index < 0: L + index else: index
if i >=% L: result = unknownLineInfo()
else: result = conf.m.msgContext[i].info
template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
if fileIdx.int32 < 0 or conf == nil:
"???"
else:
conf.m.fileInfos[fileIdx.int32].shortName
proc toProjPath*(conf: ConfigRef; fileIdx: FileIndex): string =
if fileIdx.int32 < 0 or conf == nil: "???"
else: conf.m.fileInfos[fileIdx.int32].projPath.string
proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
if fileIdx.int32 < 0 or conf == nil: result = "???"
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"???"
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) {.procvar.} =
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 hintStackTrace in conf.notes:
{.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 ..< len(conf.m.msgContext):
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.Error)
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 optWarns notin conf.options: return
if msg notin conf.notes: return
writeContext(conf, unknownLineInfo())
title = WarningTitle
color = WarningColor
kind = WarningsToStr[ord(msg) - ord(warnMin)]
inc(conf.warnCounter)
of hintMin..hintMax:
sev = Severity.Hint
if optHints notin conf.options: return
if msg notin conf.notes: 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 = optWarns notin conf.options or msg notin conf.notes
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 = optHints notin conf.options or msg notin conf.notes
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 hintSource in conf.notes:
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)]
])