diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index 40a33951c0..6c2daed426 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -573,7 +573,8 @@ template formatValue(result: var string; value: char; specifier: string) = template formatValue(result: var string; value: cstring; specifier: string) = result.add value -proc strformatImpl(f: string; openChar, closeChar: char): NimNode = +proc strformatImpl(f: string; openChar, closeChar: char, + lineInfoNode: NimNode = nil): NimNode = template missingCloseChar = error("invalid format string: missing closing character '" & closeChar & "'") @@ -581,7 +582,7 @@ proc strformatImpl(f: string; openChar, closeChar: char): NimNode = error "openChar and closeChar must not be ':'" var i = 0 let res = genSym(nskVar, "fmtRes") - result = newNimNode(nnkStmtListExpr) + result = newNimNode(nnkStmtListExpr, lineInfoNode) # XXX: https://github.com/nim-lang/Nim/issues/8405 # When compiling with -d:useNimRtl, certain procs such as `count` from the strutils # module are not accessible at compile-time: @@ -640,6 +641,7 @@ proc strformatImpl(f: string; openChar, closeChar: char): NimNode = x = parseExpr(subexpr) except ValueError as e: error("could not parse `$#` in `$#`.\n$#" % [subexpr, f, e.msg]) + x.copyLineInfo(lineInfoNode) let formatSym = bindSym("formatValue", brOpen) var options = "" if f[i] == ':': @@ -665,10 +667,22 @@ proc strformatImpl(f: string; openChar, closeChar: char): NimNode = if strlit.len > 0: result.add newCall(bindSym"add", res, newLit(strlit)) result.add res + # workaround for #20381 + var blockExpr = newNimNode(nnkBlockExpr, lineInfoNode) + blockExpr.add(newEmptyNode()) + blockExpr.add(result) + result = blockExpr when defined(debugFmtDsl): echo repr result -macro fmt*(pattern: static string; openChar: static char, closeChar: static char): string = +macro fmt(pattern: static string; openChar: static char, closeChar: static char, lineInfoNode: untyped): string = + ## version of `fmt` with dummy untyped param for line info + strformatImpl(pattern, openChar, closeChar, lineInfoNode) + +when not defined(nimHasCallsitePragma): + {.pragma: callsite.} + +template fmt*(pattern: static string; openChar: static char, closeChar: static char): string {.callsite.} = ## Interpolates `pattern` using symbols in scope. runnableExamples: let x = 7 @@ -685,13 +699,13 @@ macro fmt*(pattern: static string; openChar: static char, closeChar: static char assert "".fmt('<', '>') == "7" assert "<<>>".fmt('<', '>') == "<7>" assert "`x`".fmt('`', '`') == "7" - strformatImpl(pattern, openChar, closeChar) + fmt(pattern, openChar, closeChar, dummyForLineInfo) -template fmt*(pattern: static string): untyped = +template fmt*(pattern: static string): untyped {.callsite.} = ## Alias for `fmt(pattern, '{', '}')`. - fmt(pattern, '{', '}') + fmt(pattern, '{', '}', dummyForLineInfo) -macro `&`*(pattern: string{lit}): string = +template `&`*(pattern: string{lit}): string {.callsite.} = ## `&pattern` is the same as `pattern.fmt`. ## For a specification of the `&` macro, see the module level documentation. # pending bug #18275, bug #18278, use `pattern: static string` @@ -701,6 +715,6 @@ macro `&`*(pattern: string{lit}): string = runnableExamples: let x = 7 assert &"{x}\n" == "7\n" # regular string literal - assert &"{x}\n" == "7\n".fmt # `fmt` can be used instead - assert &"{x}\n" != fmt"7\n" # see `fmt` docs, this would use a raw string literal - strformatImpl(pattern.strVal, '{', '}') + assert &"{x}\n" == "{x}\n".fmt # `fmt` can be used instead + assert &"{x}\n" != fmt"{x}\n" # see `fmt` docs, this would use a raw string literal + fmt(pattern, '{', '}', dummyForLineInfo) diff --git a/tests/stdlib/tstrformat.nim b/tests/stdlib/tstrformat.nim index 1863d31388..b84db84bc6 100644 --- a/tests/stdlib/tstrformat.nim +++ b/tests/stdlib/tstrformat.nim @@ -555,5 +555,16 @@ proc main() = doAssert &"""{(if true: "'" & "'" & ')' else: "")}""" == "'')" doAssert &"{(if true: \"\'\" & \"'\" & ')' else: \"\")}" == "'')" doAssert fmt"""{(if true: "'" & ')' else: "")}""" == "')" + + block: # issue #20381 + var ss: seq[string] + template myTemplate(s: string) = + ss.add s + ss.add s + proc foo() = + myTemplate fmt"hello" + foo() + doAssert ss == @["hello", "hello"] + # xxx static: main() main() diff --git a/tests/stdlib/tstrformatlineinfo.nim b/tests/stdlib/tstrformatlineinfo.nim new file mode 100644 index 0000000000..3a7bf0d330 --- /dev/null +++ b/tests/stdlib/tstrformatlineinfo.nim @@ -0,0 +1,8 @@ +# issue #21759 + +{.hint[ConvFromXToItselfNotNeeded]: on.} + +import std/strformat + +echo fmt"{string ""abc""}" #[tt.Hint + ^ conversion from string to itself is pointless]#