Merge pull request #10699 from rintaro/4.0-lexer-multiline-interpolation

[4.0][Parse] Fix skipping string interpolation in Lexer
diff --git a/lib/Parse/Lexer.cpp b/lib/Parse/Lexer.cpp
index 7744930..9170c23 100644
--- a/lib/Parse/Lexer.cpp
+++ b/lib/Parse/Lexer.cpp
@@ -1244,6 +1244,9 @@
                                                      DiagnosticEngine *Diags,
                                                      bool MultilineString) {
   llvm::SmallVector<char, 4> OpenDelimiters;
+  llvm::SmallVector<bool, 4> AllowNewline;
+  AllowNewline.push_back(MultilineString);
+
   auto inStringLiteral = [&]() {
     return !OpenDelimiters.empty() &&
            (OpenDelimiters.back() == '"' || OpenDelimiters.back() == '\'');
@@ -1262,27 +1265,46 @@
     // interpolated ones are no exception - unless multiline literals.
     case '\n':
     case '\r':
-      if (MultilineString)
+      if (AllowNewline.back())
         continue;
       // Will be diagnosed as an unterminated string literal.
       return CurPtr-1;
 
     case '"':
-    case '\'':
-      if (inStringLiteral()) {
-        // Is it the closing quote?
+    case '\'': {
+      if (!AllowNewline.back() && inStringLiteral()) {
         if (OpenDelimiters.back() == CurPtr[-1]) {
+          // Closing single line string literal.
           OpenDelimiters.pop_back();
+          AllowNewline.pop_back();
         }
-        // Otherwise it's an ordinary character; treat it normally.
-      } else {
-        OpenDelimiters.push_back(CurPtr[-1]);
+        // Otherwise, it's just a quote in string literal. e.g. "foo's".
+        continue;
       }
-      if (*CurPtr == '"' && *(CurPtr + 1) == '"' && *(CurPtr - 1) == '"') {
-        MultilineString = true;
+
+      bool isMultilineQuote = (
+          *CurPtr == '"' && *(CurPtr + 1) == '"' && *(CurPtr - 1) == '"');
+      if (isMultilineQuote)
         CurPtr += 2;
+
+      if (!inStringLiteral()) {
+        // Open string literal
+        OpenDelimiters.push_back(CurPtr[-1]);
+        AllowNewline.push_back(isMultilineQuote);
+        continue;
       }
+
+      // We are in multiline string literal.
+      assert(AllowNewline.back() && "other cases must be handled above");
+      if (isMultilineQuote) {
+        // Close multiline string literal.
+        OpenDelimiters.pop_back();
+        AllowNewline.pop_back();
+      }
+
+      // Otherwise, it's just a normal character in multiline string.
       continue;
+    }
     case '\\':
       if (inStringLiteral()) {
         char escapedChar = *CurPtr++;
diff --git a/test/Parse/multiline_errors.swift b/test/Parse/multiline_errors.swift
index e58b64b..21275ee 100644
--- a/test/Parse/multiline_errors.swift
+++ b/test/Parse/multiline_errors.swift
@@ -105,4 +105,18 @@
           // expected-note@-15{{change indentation of these lines to match closing delimiter}} {{2-2=	}} {{2-2=	}}
           // expected-error@-14{{unexpected space in indentation of next 4 lines in multi-line string literal}}
           // expected-note@-7{{should match tab here}}
-          // expected-note@-16{{change indentation of these lines to match closing delimiter}} {{1-1=		}} {{1-1=		}} {{1-1=		}} {{1-1=		}}
\ No newline at end of file
+          // expected-note@-16{{change indentation of these lines to match closing delimiter}} {{1-1=		}} {{1-1=		}} {{1-1=		}} {{1-1=		}}
+
+_ = "hello\("""
+            world
+            """
+            )!"
+            // expected-error@-4 {{unterminated string literal}}
+            // expected-error@-2 {{unterminated string literal}}
+
+_ = "hello\(
+            """
+            world
+            """)!"
+            // expected-error@-4 {{unterminated string literal}}
+            // expected-error@-2 {{unterminated string literal}}
diff --git a/test/Parse/multiline_string.swift b/test/Parse/multiline_string.swift
index fc0e36a..03020c2 100644
--- a/test/Parse/multiline_string.swift
+++ b/test/Parse/multiline_string.swift
@@ -160,3 +160,24 @@
 // CHECK: "hello"
 // CHECK: "world"
 // CHECK: "\nabc"
+
+_ = "hello\("""
+            "world'
+            """)abc"
+// CHECK: "hello"
+// CHECK: "\"world'"
+// CHECK: "abc"
+
+_ = """
+    welcome
+    \(
+      "to\("""
+           Swift
+           """)"
+    )
+    !
+    """
+// CHECK: "welcome\n"
+// CHECK: "to"
+// CHECK: "Swift"
+// CHECK: "\n!"