Skip to content

Commit 382df0f

Browse files
committed
[SR-1545] Implement lexing of source conflict markers.
Lexing for conflict markers is inspired by the way clang handles them with a few updates of our own. Clang's lexer currently searches for the start of the conflict marker, attempts to find the divider points, lexes that, then finds the end. We, unfortunately, cannot be so forgiving because of operator overloads. Instead, we search for the start and end markers and ignore all text in between. Even if what is found is not conflict markers, it certainly is not valid Swift either.
1 parent 7f0ef7e commit 382df0f

File tree

4 files changed

+169
-4
lines changed

4 files changed

+169
-4
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ ERROR(lex_editor_placeholder,none,
141141
"editor placeholder in source file", ())
142142
WARNING(lex_editor_placeholder_in_playground,none,
143143
"editor placeholder in source file", ())
144-
144+
ERROR(lex_conflict_marker_in_file,none,
145+
"source control conflict marker in source file", ())
145146
//------------------------------------------------------------------------------
146147
// Declaration parsing diagnostics
147148
//------------------------------------------------------------------------------

include/swift/Parse/Lexer.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ enum class CommentRetentionMode {
3636
ReturnAsTokens,
3737
};
3838

39+
/// Kinds of conflict marker which the lexer might encounter.
40+
enum class ConflictMarkerKind {
41+
/// A normal or diff3 conflict marker, initiated by at least 7 "<"s,
42+
/// separated by at least 7 "="s or "|"s, and terminated by at least 7 ">"s.
43+
Normal,
44+
/// A Perforce-style conflict marker, initiated by 4 ">"s,
45+
/// separated by 4 "="s, and terminated by 4 "<"s.
46+
Perforce
47+
};
48+
3949
class Lexer {
4050
const LangOptions &LangOpts;
4151
const SourceManager &SourceMgr;
@@ -438,6 +448,10 @@ class Lexer {
438448

439449
void tryLexEditorPlaceholder();
440450
const char *findEndOfCurlyQuoteStringLiteral(const char*);
451+
452+
/// Try to lex conflict markers by checking for the presence of the start and
453+
/// end of the marker in diff3 or Perforce style respectively.
454+
bool tryLexConflictMarker();
441455
};
442456

443457
} // end namespace swift

lib/Parse/Lexer.cpp

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,61 @@ void Lexer::lexEscapedIdentifier() {
13731373
formToken(tok::backtick, Quote);
13741374
}
13751375

1376+
/// Find the end of a version control conflict marker.
1377+
static const char *findConflictEnd(const char *CurPtr, const char *BufferEnd,
1378+
ConflictMarkerKind CMK) {
1379+
StringRef terminator = CMK == ConflictMarkerKind::Perforce ? "<<<<\n"
1380+
: ">>>>>>> ";
1381+
size_t termLen = terminator.size();
1382+
1383+
// Get a reference to the rest of the buffer minus the length of the start
1384+
// of the conflict marker.
1385+
auto restOfBuffer = StringRef(CurPtr, BufferEnd - CurPtr).substr(termLen);
1386+
size_t endPos = restOfBuffer.find(terminator);
1387+
while (endPos != StringRef::npos) {
1388+
// Must occur at start of line.
1389+
if (endPos != 0 &&
1390+
(restOfBuffer[endPos - 1] == '\r' || restOfBuffer[endPos - 1] == '\n'))
1391+
{
1392+
return restOfBuffer.data() + endPos;
1393+
}
1394+
restOfBuffer = restOfBuffer.substr(endPos + termLen);
1395+
endPos = restOfBuffer.find(terminator);
1396+
}
1397+
return nullptr;
1398+
}
1399+
1400+
bool Lexer::tryLexConflictMarker() {
1401+
const char *Ptr = CurPtr - 1;
1402+
1403+
// Only a conflict marker if it starts at the beginning of a line.
1404+
if (Ptr != BufferStart && Ptr[-1] != '\n' && Ptr[-1] != '\r')
1405+
return false;
1406+
1407+
// Check to see if we have <<<<<<< or >>>>.
1408+
StringRef restOfBuffer(Ptr, BufferEnd - Ptr);
1409+
if (!restOfBuffer.startswith("<<<<<<< ") && !restOfBuffer.startswith(">>>> "))
1410+
return false;
1411+
1412+
ConflictMarkerKind Kind = *Ptr == '<' ? ConflictMarkerKind::Normal
1413+
: ConflictMarkerKind::Perforce;
1414+
if (const char *End = findConflictEnd(Ptr, BufferEnd, Kind)) {
1415+
// Diagnose at the conflict marker, then jump ahead to the end.
1416+
diagnose(CurPtr, diag::lex_conflict_marker_in_file);
1417+
CurPtr = End;
1418+
1419+
// Skip ahead to the end of the marker.
1420+
if (CurPtr != BufferEnd)
1421+
skipToEndOfLine();
1422+
1423+
return true;
1424+
}
1425+
1426+
// No end of conflict marker found.
1427+
return false;
1428+
}
1429+
1430+
13761431
void Lexer::tryLexEditorPlaceholder() {
13771432
assert(CurPtr[-1] == '<' && CurPtr[0] == '#');
13781433
const char *TokStart = CurPtr-1;
@@ -1689,10 +1744,17 @@ void Lexer::lexImpl() {
16891744
case '<':
16901745
if (CurPtr[0] == '#')
16911746
return tryLexEditorPlaceholder();
1692-
SWIFT_FALLTHROUGH;
1747+
else if (CurPtr[0] == '<' && tryLexConflictMarker())
1748+
goto Restart;
1749+
return lexOperatorIdentifier();
16931750

1694-
case '=': case '-': case '+': case '*': case '>':
1695-
case '&': case '|': case '^': case '~': case '.':
1751+
case '>':
1752+
if (CurPtr[0] == '>' && tryLexConflictMarker())
1753+
goto Restart;
1754+
return lexOperatorIdentifier();
1755+
1756+
case '=': case '-': case '+': case '*':
1757+
case '&': case '|': case '^': case '~': case '.':
16961758
return lexOperatorIdentifier();
16971759

16981760
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':

test/Parse/conflict_markers.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// RUN: %target-parse-verify-swift
2+
3+
// Conflict marker parsing should never conflict with operator parsing.
4+
5+
prefix operator <<<<<<< {}
6+
infix operator <<<<<<< {}
7+
8+
prefix func <<<<<<< (x : String) {}
9+
func <<<<<<< (x : String, y : String) {}
10+
11+
prefix operator >>>>>>> {}
12+
infix operator >>>>>>> {}
13+
14+
prefix func >>>>>>> (x : String) {}
15+
func >>>>>>> (x : String, y : String) {}
16+
17+
// diff3-style conflict markers
18+
19+
<<<<<<< HEAD:conflict_markers.swift // expected-error {{source control conflict marker in source file}}
20+
var a : String = "A"
21+
var b : String = "b"
22+
=======
23+
var a : String = "a"
24+
var b : String = "B"
25+
>>>>>>> 18844bc65229786b96b89a9fc7739c0fc897905e:conflict_markers.swift
26+
print(a + b) // expected-error {{use of unresolved identifier 'a'}} expected-error {{use of unresolved identifier 'b'}}
27+
28+
<<<<<<< HEAD:conflict_markers.swift // expected-error {{source control conflict marker in source file}}
29+
=======
30+
var d : String = "D"
31+
>>>>>>> 18844bc65229786b96b89a9fc7739c0fc897905e:conflict_markers.swift
32+
print(d) // expected-error {{use of unresolved identifier 'd'}}
33+
34+
<<<<<<<"HEAD:fake_conflict_markers.swift" // No error
35+
>>>>>>>"18844bc65229786b96b89a9fc7739c0fc897905e:fake_conflict_markers.swift" // No error
36+
37+
<<<<<<< HEAD:conflict_markers.swift // expected-error {{source control conflict marker in source file}}
38+
<<<<<<<"HEAD:fake_conflict_markers.swift"
39+
var fake_b : String = "a"
40+
>>>>>>>"18844bc65229786b96b89a9fc7739c0fc897905e:fake_conflict_markers.swift"
41+
=======
42+
<<<<<<<"HEAD:fake_conflict_markers.swift"
43+
var fake_c : String = "a"
44+
>>>>>>>"18844bc65229786b96b89a9fc7739c0fc897905e:fake_conflict_markers.swift"
45+
>>>>>>> 18844bc65229786b96b89a9fc7739c0fc897905e:conflict_markers.swift
46+
print(fake_b + fake_c) // expected-error {{use of unresolved identifier 'fake_b'}} expected-error {{use of unresolved identifier 'fake_c'}}
47+
48+
// Disambiguating conflict markers from operator applications.
49+
50+
_ = {
51+
52+
// Conflict marker.
53+
54+
let a = "a", b = "b"
55+
a // expected-warning {{expression of type 'String' is unused}}
56+
<<<<<<< b // expected-error {{source control conflict marker in source file}}
57+
a
58+
>>>>>>> b
59+
60+
// Not a conflict marker.
61+
62+
a
63+
<<<<<<< b
64+
a
65+
>>>>>>> b
66+
}()
67+
68+
// Perforce-style conflict markers
69+
70+
>>>> ORIGINAL // expected-error {{source control conflict marker in source file}}
71+
var a : String = "A"
72+
var b : String = "B"
73+
==== THEIRS
74+
var a : String = "A"
75+
var b : String = "b"
76+
==== YOURS
77+
var a : String = "a"
78+
var b : String = "B"
79+
<<<<
80+
print(a + b) // expected-error {{use of unresolved identifier 'a'}} expected-error {{use of unresolved identifier 'b'}}
81+
82+
>>>> ORIGINAL // expected-error {{source control conflict marker in source file}}
83+
==== THEIRS
84+
==== YOURS
85+
var d : String = "D"
86+
<<<<
87+
print(d) // expected-error {{use of unresolved identifier 'd'}}
88+

0 commit comments

Comments
 (0)