Skip to content

Commit cb3d355

Browse files
committed
Fix handling of multi-prefix checks
1 parent bcf40f5 commit cb3d355

File tree

4 files changed

+174
-94
lines changed

4 files changed

+174
-94
lines changed

Sources/FileCheck/CheckString.swift

Lines changed: 93 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ struct CheckString {
8080
// Match itself from the last position after matching CHECK-DAG.
8181
let matchBuffer = buffer.substring(from: buffer.index(buffer.startIndex, offsetBy: lastPos))
8282
guard let (range, mutVariableTable) = self.pattern.match(matchBuffer, initialTable) else {
83-
diagnoseFailedCheck(variableTable, options, buffer)
83+
diagnoseFailedCheck(self.pattern, self.loc, self.prefix, variableTable, options, buffer)
8484
return nil
8585
}
8686
let (matchPos, matchLen) = (range.location, range.length)
@@ -251,7 +251,7 @@ struct CheckString {
251251
// With a group of CHECK-DAGs, a single mismatching means the match on
252252
// that group of CHECK-DAGs fails immediately.
253253
guard let (range, variableTable) = pattern.match(matchBuffer, finalTable) else {
254-
// PrintCheckFailed(SM, Pat.getLoc(), Pat, MatchBuffer, VariableTable)
254+
diagnoseFailedCheck(pattern, pattern.patternLoc, self.prefix, finalTable, options, matchBuffer)
255255
return nil
256256
}
257257
finalTable = variableTable
@@ -300,112 +300,116 @@ struct CheckString {
300300

301301
return (lastPos, notStrings, finalTable)
302302
}
303+
}
304+
305+
private func diagnoseFailedCheck(
306+
_ pattern: Pattern, _ loc: CheckLoc, _ prefix: String,
307+
_ variableTable: [String : String], _ options: FileCheckOptions,
308+
_ buffer: String
309+
) {
310+
if let rtm = pattern.computeRegexToMatch(variableTable) {
311+
if !pattern.fixedString.isEmpty {
312+
diagnose(.error,
313+
at: loc,
314+
with: prefix + ": could not find '\(pattern.fixedString)' (with regex '\(rtm)') in input",
315+
options: options
316+
)
317+
} else {
318+
diagnose(.error,
319+
at: loc,
320+
with: prefix + ": could not find a match for regex '\(rtm)' in input",
321+
options: options
322+
)
323+
}
324+
} else {
325+
diagnose(.error,
326+
at: loc,
327+
with: prefix + ": could not find '\(pattern.fixedString)' in input",
328+
options: options
329+
)
330+
}
303331

304-
private func diagnoseFailedCheck(_ variableTable: [String : String], _ options: FileCheckOptions, _ buffer: String) {
305-
if let rtm = self.pattern.computeRegexToMatch(variableTable) {
306-
if !self.pattern.fixedString.isEmpty {
307-
diagnose(.error,
308-
at: self.loc,
309-
with: self.prefix + ": could not find '\(self.pattern.fixedString)' (with regex '\(rtm)') in input",
332+
// Note any variables used by the pattern
333+
for (varName, _) in pattern.variableUses {
334+
if varName.characters.first == "@" {
335+
// If we failed with a builtin variable like @LINE, try to report
336+
// what it is bound to.
337+
if let value = pattern.evaluateExpression(varName) {
338+
diagnose(.note,
339+
at: loc,
340+
with: "with expression '\(varName)' equal to '\(value)'",
310341
options: options
311342
)
312343
} else {
313-
diagnose(.error,
314-
at: self.loc,
315-
with: self.prefix + ": could not find a match for regex '\(rtm)' in input",
344+
// If evaluation fails, we must have an incorrect builtin variable.
345+
diagnose(.note,
346+
at: loc,
347+
with: "uses incorrect expression '\(varName)'",
316348
options: options
317349
)
318350
}
319351
} else {
320-
diagnose(.error,
321-
at: self.loc,
322-
with: self.prefix + ": could not find '\(self.pattern.fixedString)' in input",
323-
options: options
324-
)
325-
}
326-
327-
// Note any variables used by the pattern
328-
for (varName, _) in self.pattern.variableUses {
329-
if varName.characters.first == "@" {
330-
// If we failed with a builtin variable like @LINE, try to report
331-
// what it is bound to.
332-
if let value = self.pattern.evaluateExpression(varName) {
333-
diagnose(.note,
334-
at: self.loc,
335-
with: "with expression '\(varName)' equal to '\(value)'",
336-
options: options
337-
)
338-
} else {
339-
// If evaluation fails, we must have an incorrect builtin variable.
340-
diagnose(.note,
341-
at: self.loc,
342-
with: "uses incorrect expression '\(varName)'",
343-
options: options
344-
)
345-
}
352+
if let varDef = variableTable[varName] {
353+
diagnose(.note,
354+
at: loc,
355+
with: "with variable '\(varName)' equal to '\(varDef)'",
356+
options: options
357+
)
346358
} else {
347-
if let varDef = self.pattern.variableDefs[varName] {
348-
diagnose(.note,
349-
at: self.loc,
350-
with: "with variable '\(varName)' equal to '\(varDef)'",
351-
options: options
352-
)
353-
} else {
354-
diagnose(.note,
355-
at: self.loc,
356-
with: "uses undefined variable '\(varName)'",
357-
options: options
358-
)
359-
}
359+
diagnose(.note,
360+
at: loc,
361+
with: "uses undefined variable '\(varName)'",
362+
options: options
363+
)
360364
}
361365
}
366+
}
362367

363-
var NumLinesForward = 0
364-
var BestLine : Int? = nil
365-
var BestQuality = 0.0
368+
var NumLinesForward = 0
369+
var BestLine : Int? = nil
370+
var BestQuality = 0.0
366371

367-
for i in 0..<min(buffer.characters.count, 4096) {
368-
let exampleString : String
369-
if pattern.fixedString.isEmpty {
370-
exampleString = pattern.regExPattern
371-
} else {
372-
exampleString = pattern.fixedString
373-
}
372+
for i in 0..<min(buffer.characters.count, 4096) {
373+
let exampleString : String
374+
if pattern.fixedString.isEmpty {
375+
exampleString = pattern.regExPattern
376+
} else {
377+
exampleString = pattern.fixedString
378+
}
374379

375-
if exampleString.isEmpty {
376-
break
377-
}
380+
if exampleString.isEmpty {
381+
break
382+
}
378383

379-
let char = buffer[buffer.index(buffer.startIndex, offsetBy: i)]
380-
if char == "\n" {
381-
NumLinesForward += 1
382-
}
384+
let char = buffer[buffer.index(buffer.startIndex, offsetBy: i)]
385+
if char == "\n" {
386+
NumLinesForward += 1
387+
}
383388

384-
// Patterns have leading whitespace stripped, so skip whitespace when
385-
// looking for something which looks like a pattern.
386-
if char == " " || char == "\t" {
387-
continue;
388-
}
389+
// Patterns have leading whitespace stripped, so skip whitespace when
390+
// looking for something which looks like a pattern.
391+
if char == " " || char == "\t" {
392+
continue;
393+
}
389394

390-
// Compute the "quality" of this match as an arbitrary combination of
391-
// the match distance and the number of lines skipped to get to this
392-
// match.
393-
let distance = editDistance(from: Array(buffer.characters), to: Array(exampleString.characters))
394-
let quality = Double(distance) + (Double(NumLinesForward) / 100.0)
395-
if quality < BestQuality || BestLine == nil {
396-
BestLine = i
397-
BestQuality = quality
398-
}
395+
// Compute the "quality" of this match as an arbitrary combination of
396+
// the match distance and the number of lines skipped to get to this
397+
// match.
398+
let distance = editDistance(from: Array(buffer.characters), to: Array(exampleString.characters))
399+
let quality = Double(distance) + (Double(NumLinesForward) / 100.0)
400+
if quality < BestQuality || BestLine == nil {
401+
BestLine = i
402+
BestQuality = quality
399403
}
404+
}
400405

401-
if let Best = BestLine, BestQuality < 50 {
402-
buffer.utf8CString.withUnsafeBufferPointer { buf in
403-
let otherPatternLoc = CheckLoc.inBuffer(
404-
buf.baseAddress!.advanced(by: Best),
405-
UnsafeBufferPointer(start: buf.baseAddress?.advanced(by: Best), count: buf.count - Best)
406-
)
407-
diagnose(.note, at: otherPatternLoc, with: "possible intended match here", options: options)
408-
}
406+
if let Best = BestLine, BestQuality < 50 {
407+
buffer.utf8CString.withUnsafeBufferPointer { buf in
408+
let otherPatternLoc = CheckLoc.inBuffer(
409+
buf.baseAddress!.advanced(by: Best),
410+
UnsafeBufferPointer(start: buf.baseAddress?.advanced(by: Best), count: buf.count - Best)
411+
)
412+
diagnose(.note, at: otherPatternLoc, with: "possible intended match here", options: options)
409413
}
410414
}
411415
}

Sources/FileCheck/FileCheck.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public func fileCheckOutput(of FD : FileCheckFD = .stdout, withPrefixes prefixes
9797
"hyphens and underscores")
9898
return false
9999
}
100-
guard let prefixRE = try? NSRegularExpression(pattern: validPrefixes.joined(separator: "|"), options: []) else {
100+
guard let prefixRE = try? NSRegularExpression(pattern: validPrefixes.sorted(by: >).joined(separator: "|"), options: []) else {
101101
print("Unable to combine check-prefix strings into a prefix regular ",
102102
"expression! This is likely a bug in FileCheck's verification of ",
103103
"the check-prefix strings. Regular expression parsing failed.")

Tests/FileCheckTests/LabelSpec.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import XCTest
33
import Foundation
44

55
class LabelSpec : XCTestCase {
6-
let output = {
6+
let outputABC = {
77
print([
88
"label0:",
99
"a",
@@ -32,11 +32,11 @@ class LabelSpec : XCTestCase {
3232
// CHECKOK-LABEL: {{^}}label2:
3333
// CHECKOK: {{^}}a
3434
// CHECKOK: {{^}}c
35-
output()
35+
outputABC()
3636
})
3737
}
3838

39-
func labelFail() {
39+
func testLabelFail() {
4040
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECKERROR"]) {
4141
// CHECKERROR: error: CHECKFAIL: could not find a match for regex '(^)c' in input
4242
//
@@ -58,8 +58,28 @@ class LabelSpec : XCTestCase {
5858
// CHECKFAIL: {{^}}a
5959
// CHECKFAIL: {{^}}b
6060
// CHECKFAIL: {{^}}c
61-
output()
61+
outputABC()
6262
})
6363
})
6464
}
65+
66+
func testLabelDAG() {
67+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECKLABELDAG-ERROR"]) {
68+
// CHECKLABELDAG-ERROR: error: CHECKLABELDAG: could not find a match for regex '(^)foo' in input
69+
XCTAssertFalse(fileCheckOutput(of: .stdout, withPrefixes: ["CHECKLABELDAG"], options: [.disableColors]) {
70+
// CHECKLABELDAG-LABEL: {{^}}bar
71+
// CHECKLABELDAG-DAG: {{^}}foo
72+
// CHECKLABELDAG-LABEL: {{^}}zed
73+
print(["bar", "zed"].joined(separator: "\n"))
74+
})
75+
})
76+
77+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECKLABELDAGCAPTURE"], options: [.disableColors]) {
78+
// CHECKLABELDAGCAPTURE-LABEL: {{^}}bar
79+
// CHECKLABELDAGCAPTURE: {{^}}[[FOO:foo]]
80+
// CHECKLABELDAGCAPTURE-DAG: {{^}}[[FOO]]
81+
// CHECKLABELDAGCAPTURE-LABEL: {{^}}zed
82+
print(["bar", "foo", "foo", "zed"].joined(separator: "\n"))
83+
})
84+
}
6585
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import FileCheck
2+
import XCTest
3+
import Foundation
4+
5+
class MultiPrefixSpec : XCTestCase {
6+
func testMultiplePrefixSubstr() {
7+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECKER1", "CHECK"]) {
8+
// CHECKER1: fo{{o}}
9+
print("foo")
10+
})
11+
12+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["CHECK", "CHECKER2"]) {
13+
// CHECKER2: fo{{o}}
14+
print("foo")
15+
})
16+
}
17+
18+
func testMultiPrefixMixed() {
19+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["B1", "BOTH1"]) {
20+
// A1: {{a}}aaaaa
21+
// B1: {{b}}bbbb
22+
// BOTH: {{q}}qqqqq
23+
print([
24+
"aaaaaa",
25+
"bbbbb",
26+
"qqqqqq",
27+
"ccccc",
28+
])
29+
})
30+
31+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["A2", "BOTH2"]) {
32+
// A2: {{a}}aaaaa
33+
// B2: {{b}}bbbb
34+
// BOTH2: {{q}}qqqqq
35+
print([
36+
"aaaaaa",
37+
"bbbbb",
38+
"qqqqqq",
39+
"ccccc",
40+
])
41+
})
42+
}
43+
44+
func testMultiplePrefixNoMatch() {
45+
XCTAssert(fileCheckOutput(of: .stdout, withPrefixes: ["MULTI-PREFIX-ERR"]) {
46+
// MULTI-PREFIX-ERR: error: FOO: could not find a match for regex 'fo(o)' in input
47+
XCTAssertFalse(fileCheckOutput(of: .stdout, withPrefixes: ["FOO", "BAR"], options: [.disableColors]) {
48+
// _FOO not a valid check-line
49+
// FOO: fo{{o}}
50+
// BAR: ba{{r}}
51+
print(["fog", "bar"].joined(separator: "\n"))
52+
})
53+
})
54+
}
55+
}
56+

0 commit comments

Comments
 (0)