Skip to content

Commit

Permalink
Lex single-quote string literals but emit an error if they're encount…
Browse files Browse the repository at this point in the history
…ered.

Emit a fix-it replacing them with double-quote string literals.

<rdar://problem/21950709> QoI: Parse single-quoted literals like double-quoted literals

Swift SVN r31973
  • Loading branch information
cwillmor committed Sep 15, 2015
1 parent dd97410 commit 4b8a5cf
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 21 deletions.
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ ERROR(lex_unprintable_ascii_character,lexing,none,
"unprintable ASCII character found in source file", ())
ERROR(lex_invalid_utf8,lexing,none,
"invalid UTF-8 found in source file", ())
ERROR(lex_single_quote_string,lexing,none,
"single-quoted string literal found, use '\"'", ())
ERROR(lex_invalid_curly_quote,lexing,none,
"unicode curly quote found, replace with '\"'", ())

Expand Down
2 changes: 1 addition & 1 deletion include/swift/Parse/Lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ class Lexer {
static unsigned lexUnicodeEscape(const char *&CurPtr, Lexer *Diags);

unsigned lexCharacter(const char *&CurPtr,
bool StopAtDoubleQuote, bool EmitDiagnostics);
char StopQuote, bool EmitDiagnostics);
void lexStringLiteral();
void lexEscapedIdentifier();

Expand Down
58 changes: 38 additions & 20 deletions lib/Parse/Lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -914,13 +914,14 @@ unsigned Lexer::lexUnicodeEscape(const char *&CurPtr, Lexer *Diags) {


/// lexCharacter - Read a character and return its UTF32 code. If this is the
/// end of enclosing string/character sequence, this returns ~0U. If this is a
/// malformed character sequence, it emits a diagnostic (when EmitDiagnostics is
/// true) and returns ~1U.
/// end of enclosing string/character sequence (i.e. the character is equal to
/// 'StopQuote'), this returns ~0U and leaves 'CurPtr' pointing to the terminal
/// quote. If this is a malformed character sequence, it emits a diagnostic
/// (when EmitDiagnostics is true) and returns ~1U.
///
/// character_escape ::= [\][\] | [\]t | [\]n | [\]r | [\]" | [\]' | [\]0
/// character_escape ::= unicode_character_escape
unsigned Lexer::lexCharacter(const char *&CurPtr, bool StopAtDoubleQuote,
unsigned Lexer::lexCharacter(const char *&CurPtr, char StopQuote,
bool EmitDiagnostics) {
const char *CharStart = CurPtr;

Expand All @@ -941,19 +942,13 @@ unsigned Lexer::lexCharacter(const char *&CurPtr, bool StopAtDoubleQuote,
return ~1U;
}
case '"':
// If we found the closing " character, we're done.
if (StopAtDoubleQuote) {
--CurPtr;
return ~0U;
}
// In a single quoted string, this is just a character.
return CurPtr[-1];
case '\'':
if (!StopAtDoubleQuote) {
--CurPtr;
// If we found a closing quote character, we're done.
if (CurPtr[-1] == StopQuote) {
--CurPtr;
return ~0U;
}
// In a double quoted string, this is just a character.
// Otherwise, this is just a character.
return CurPtr[-1];

case 0:
Expand Down Expand Up @@ -1033,7 +1028,8 @@ static const char *skipToEndOfInterpolatedExpression(const char *CurPtr,
DiagnosticEngine *Diags) {
llvm::SmallVector<char, 4> OpenDelimiters;
auto inStringLiteral = [&]() {
return !OpenDelimiters.empty() && OpenDelimiters.back() == '"';
return !OpenDelimiters.empty() &&
(OpenDelimiters.back() == '"' || OpenDelimiters.back() == '\'');
};
while (true) {
// This is a simple scanner, capable of recognizing nested parentheses and
Expand All @@ -1053,10 +1049,15 @@ static const char *skipToEndOfInterpolatedExpression(const char *CurPtr,
return CurPtr-1;

case '"':
case '\'':
if (inStringLiteral()) {
OpenDelimiters.pop_back();
// Is it the closing quote?
if (OpenDelimiters.back() == CurPtr[-1]) {
OpenDelimiters.pop_back();
}
// Otherwise it's an ordinary character; treat it normally.
} else {
OpenDelimiters.push_back('"');
OpenDelimiters.push_back(CurPtr[-1]);
}
continue;
case '\\':
Expand Down Expand Up @@ -1115,7 +1116,9 @@ static const char *skipToEndOfInterpolatedExpression(const char *CurPtr,
/// string_literal ::= ["]([^"\\\n\r]|character_escape)*["]
void Lexer::lexStringLiteral() {
const char *TokStart = CurPtr-1;
assert(*TokStart == '"' && "Unexpected start");
assert((*TokStart == '"' || *TokStart == '\'') && "Unexpected start");
// NOTE: We only allow single-quote string literals so we can emit useful
// diagnostics about changing them to double quotes.

bool wasErroneous = false;

Expand All @@ -1142,7 +1145,7 @@ void Lexer::lexStringLiteral() {
return formToken(tok::unknown, TokStart);
}

unsigned CharValue = lexCharacter(CurPtr, true, true);
unsigned CharValue = lexCharacter(CurPtr, *TokStart, true);
wasErroneous |= CharValue == ~1U;

// If this is the end of string, we are done. If it is a normal character
Expand All @@ -1151,6 +1154,20 @@ void Lexer::lexStringLiteral() {
CurPtr++;
if (wasErroneous)
return formToken(tok::unknown, TokStart);

if (*TokStart == '\'') {
// Complain about single-quote string and suggest replacement with
// double-quoted equivalent.
// FIXME: Fixit should replace ['"'] with ["\""] (radar 22709931)
StringRef orig(TokStart, CurPtr - TokStart);
llvm::SmallString<32> replacement;
replacement += '"';
replacement += orig.slice(1, orig.size() - 1);
replacement += '"';
diagnose(TokStart, diag::lex_single_quote_string)
.fixItReplaceChars(getSourceLoc(TokStart), getSourceLoc(CurPtr),
replacement);
}
return formToken(tok::string_literal, TokStart);
}
}
Expand All @@ -1175,7 +1192,7 @@ const char *Lexer::findEndOfCurlyQuoteStringLiteral(const char *Body) {

// Get the next character.
const char *CharStart = Body;
unsigned CharValue = lexCharacter(Body, false, false);
unsigned CharValue = lexCharacter(Body, '\0', false);
// If the character was incorrectly encoded, give up.
if (CharValue == ~1U) return nullptr;

Expand Down Expand Up @@ -1613,6 +1630,7 @@ void Lexer::lexImpl() {
return lexNumber();

case '"':
case '\'':
return lexStringLiteral();

case '`':
Expand Down
21 changes: 21 additions & 0 deletions test/expr/expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,27 @@ func stringliterals(d: [String: Int]) {
; // expected-error {{expected expression in list of expressions}}
} // expected-error {{expected ')' in expression list}}
func testSingleQuoteStringLiterals() {
'abc' // expected-error{{single-quoted string literal found, use '"'}}{{3-8="abc"}}
_ = 'abc' + "def" // expected-error{{single-quoted string literal found, use '"'}}{{7-12="abc"}}

'ab\nc' // expected-error{{single-quoted string literal found, use '"'}}{{3-10="ab\\nc"}}

"abc\('def')" // expected-error{{single-quoted string literal found, use '"'}}{{9-14="def"}}

"abc' // expected-error{{unterminated string literal}}
'abc" // expected-error{{unterminated string literal}}
"a'c"

// FIXME: <rdar://problem/22709931> QoI: Single-quote => double-quote string literal fixit should escape quote chars
// FIXME: The suggested replacement should un-escape the single quote
// character.
'ab\'c' // expected-error{{single-quoted string literal found, use '"'}}{{3-10="ab\\'c"}}

// FIXME: The suggested replacement should escape the double-quote character.
'ab"c' // expected-error{{single-quoted string literal found, use '"'}}{{3-9="ab"c"}}
}

// <rdar://problem/17128913>
var s = ""
s.appendContentsOf(["x"])
Expand Down

0 comments on commit 4b8a5cf

Please sign in to comment.