[utils] escape backslashes in diagnostics strings

DiagnosticVerifier lexes the contents of expected diagnostics the same
way Swift parses string literals, to allow for escape codes. This makes
for problems when backslashes are followed by something that makes for a
valid escape. In particular, it reaches an llvm_unreachable if it
encounters `\(`, which would create a string interpolation in a string
literal.
This commit is contained in:
Henrik G. Olsson
2025-11-14 22:16:50 -08:00
parent 9c989971af
commit 357c298940
2 changed files with 53 additions and 2 deletions

View File

@@ -42,6 +42,11 @@
// RUN: not %target-swift-frontend-verify -I %t -plugin-path %swift-plugin-dir -typecheck %t/unparsed.swift 2>%t/output.txt -Rmacro-expansions
// RUN: not %update-verify-tests < %t/output.txt | %FileCheck --check-prefix CHECK-UNPARSED %s
// RUN: not %target-swift-frontend-verify -load-plugin-library %t/%target-library-name(UnstringifyMacroDefinition) -typecheck %t/escaped.swift 2>%t/output.txt -Rmacro-expansions
// RUN: %update-verify-tests < %t/output.txt
// RUN: %target-swift-frontend-verify -load-plugin-library %t/%target-library-name(UnstringifyMacroDefinition) -typecheck %t/escaped.swift -Rmacro-expansions
// RUN: %diff %t/escaped.swift %t/escaped.swift.expected
//--- single.swift
@attached(peer, names: overloaded)
macro unstringifyPeer(_ s: String) =
@@ -260,3 +265,43 @@ func bar() {
foo(a, &b)
}
//--- escaped.swift
@attached(peer, names: overloaded)
macro unstringifyPeer(_ s: String) =
#externalMacro(module: "UnstringifyMacroDefinition", type: "UnstringifyPeerMacro")
@unstringifyPeer("""
func foo(_ x: Int) {
let a = "\\(x)"
let b = "\\(x)"
}
""")
// NB: DiagnosticVerifier interprets "\\(x)" as "\(x)"
// expected-expansion@+3:30{{
// expected-remark@2{{macro content: |let a = "\\(x)"|}}
// }}
func foo() { let _ = "\(2)" }
//--- escaped.swift.expected
@attached(peer, names: overloaded)
macro unstringifyPeer(_ s: String) =
#externalMacro(module: "UnstringifyMacroDefinition", type: "UnstringifyPeerMacro")
// expected-note@+1 6{{in expansion of macro 'unstringifyPeer' on global function 'foo()' here}}
@unstringifyPeer("""
func foo(_ x: Int) {
let a = "\\(x)"
let b = "\\(x)"
}
""")
// NB: DiagnosticVerifier interprets "\\(x)" as "\(x)"
// expected-expansion@+8:30{{
// expected-remark@1{{macro content: |func foo(_ x: Int) {|}}
// expected-warning@2{{initialization of immutable value 'a' was never used; consider replacing with assignment to '_' or removing it}}
// expected-remark@2{{macro content: | let a = "\\(x)"|}}
// expected-warning@3{{initialization of immutable value 'b' was never used; consider replacing with assignment to '_' or removing it}}
// expected-remark@3{{macro content: | let b = "\\(x)"|}}
// expected-remark@4{{macro content: |}|}}
// }}
func foo() { let _ = "\(2)" }

View File

@@ -1,5 +1,6 @@
import sys
import re
from codecs import encode, decode
DEBUG = False
@@ -172,7 +173,9 @@ class Diag:
if self.category == "expansion":
return base_s + "{{"
else:
return base_s + "{{" + self.diag_content + "}}"
# python trivia: raw strings can't end with a backslash
escaped_diag_s = self.diag_content.replace("\\", "\\\\")
return base_s + "{{" + escaped_diag_s + "}}"
class ExpansionDiagClose:
@@ -245,9 +248,12 @@ def parse_diag(line, filename, prefix):
count = int(count_s) if count_s else 1
line.content = matched_re.sub("{{DIAG}}", s)
unescaped_diag_s = decode(
encode(diag_s, "utf-8", "backslashreplace"), "unicode-escape"
)
return Diag(
check_prefix,
diag_s,
unescaped_diag_s,
category_s,
target_line_n,
is_absolute,