Skip to content

Commit a4e5bce

Browse files
committed
Move strange interpolation fix to PreCheckExpression
This defers diagnosis until a stage where #if conditions have definitely been evaluated, at the cost of a slightly more complex implementation. We’ll gain some of that complexity back in a subsequent refactoring. Fixes SR-9937.
1 parent 59d2fea commit a4e5bce

File tree

4 files changed

+130
-31
lines changed

4 files changed

+130
-31
lines changed

lib/Parse/ParseExpr.cpp

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,37 +1927,6 @@ parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
19271927

19281928
InterpolationCount += 1;
19291929

1930-
// In Swift 4.2 and earlier, a single argument with a label would ignore
1931-
// the label (at best), and multiple arguments would form a tuple.
1932-
if (!Context.isSwiftVersionAtLeast(5)) {
1933-
if (args.size() > 1) {
1934-
diagnose(args[1]->getLoc(), diag::string_interpolation_list_changing)
1935-
.highlightChars(args[1]->getLoc(), rParen);
1936-
diagnose(args[1]->getLoc(),
1937-
diag::string_interpolation_list_insert_parens)
1938-
.fixItInsertAfter(lParen, "(")
1939-
.fixItInsert(rParen, ")");
1940-
1941-
args = {
1942-
TupleExpr::create(Context,
1943-
lParen, args, argLabels, argLabelLocs, rParen,
1944-
/*hasTrailingClosure=*/false,
1945-
/*Implicit=*/false)
1946-
};
1947-
argLabels = { Identifier() };
1948-
argLabelLocs = { SourceLoc() };
1949-
}
1950-
else if (args.size() == 1 && argLabels[0] != Identifier()) {
1951-
diagnose(argLabelLocs[0], diag::string_interpolation_label_changing)
1952-
.highlightChars(argLabelLocs[0], args[0]->getStartLoc());
1953-
diagnose(argLabelLocs[0], diag::string_interpolation_remove_label,
1954-
argLabels[0])
1955-
.fixItRemoveChars(argLabelLocs[0], args[0]->getStartLoc());
1956-
1957-
argLabels[0] = Identifier();
1958-
}
1959-
}
1960-
19611930
auto AppendInterpolationRef =
19621931
new (Context) UnresolvedDotExpr(InterpolationVarRef,
19631932
/*dotloc=*/SourceLoc(),

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,107 @@ namespace {
952952
/// the type conforms to the expected literal protocol.
953953
Expr *simplifyTypeConstructionWithLiteralArg(Expr *E);
954954

955+
/// In Swift < 5, diagnose and correct invalid multi-argument or
956+
/// argument-labeled interpolations.
957+
void correctInterpolationIfStrange(InterpolatedStringLiteralExpr *ISLE) {
958+
// These expressions are valid in Swift 5+.
959+
if (TC.Context.isSwiftVersionAtLeast(5))
960+
return;
961+
962+
/// Diagnoses appendInterpolation(...) calls with multiple
963+
/// arguments or argument labels and corrects them.
964+
class StrangeInterpolationRewriter : public ASTWalker {
965+
TypeChecker &TC;
966+
967+
public:
968+
StrangeInterpolationRewriter(TypeChecker &TC) : TC(TC) { }
969+
970+
virtual bool walkToDeclPre(Decl *D) {
971+
// We don't want to look inside decls.
972+
return false;
973+
}
974+
975+
virtual std::pair<bool, Expr *> walkToExprPre(Expr *E) {
976+
// One InterpolatedStringLiteralExpr should never be nested inside
977+
// another except as a child of a CallExpr, and we don't recurse into
978+
// the children of CallExprs.
979+
assert(!isa<InterpolatedStringLiteralExpr>(E) &&
980+
"StrangeInterpolationRewriter found nested interpolation?");
981+
982+
// We only care about CallExprs.
983+
if (!isa<CallExpr>(E))
984+
return { true, E };
985+
986+
auto call = cast<CallExpr>(E);
987+
if (auto callee = dyn_cast<UnresolvedDotExpr>(call->getFn())) {
988+
if (callee->getName().getBaseName() ==
989+
TC.Context.Id_appendInterpolation) {
990+
Expr *newArg = nullptr;
991+
SourceLoc lParen, rParen;
992+
993+
if (call->getNumArguments() > 1) {
994+
auto *args = cast<TupleExpr>(call->getArg());
995+
996+
lParen = args->getLParenLoc();
997+
rParen = args->getRParenLoc();
998+
Expr *secondArg = args->getElement(1);
999+
1000+
TC.diagnose(secondArg->getLoc(),
1001+
diag::string_interpolation_list_changing)
1002+
.highlightChars(secondArg->getLoc(), rParen);
1003+
TC.diagnose(secondArg->getLoc(),
1004+
diag::string_interpolation_list_insert_parens)
1005+
.fixItInsertAfter(lParen, "(")
1006+
.fixItInsert(rParen, ")");
1007+
1008+
newArg = args;
1009+
}
1010+
else if(call->getNumArguments() == 1 &&
1011+
call->getArgumentLabels().front() != Identifier()) {
1012+
auto *args = cast<TupleExpr>(call->getArg());
1013+
newArg = args->getElement(0);
1014+
1015+
lParen = args->getLParenLoc();
1016+
rParen = args->getRParenLoc();
1017+
1018+
SourceLoc argLabelLoc = call->getArgumentLabelLoc(0),
1019+
argLoc = newArg->getStartLoc();
1020+
1021+
TC.diagnose(argLabelLoc,
1022+
diag::string_interpolation_label_changing)
1023+
.highlightChars(argLabelLoc, argLoc);
1024+
TC.diagnose(argLabelLoc,
1025+
diag::string_interpolation_remove_label,
1026+
call->getArgumentLabels().front())
1027+
.fixItRemoveChars(argLabelLoc, argLoc);
1028+
}
1029+
1030+
// If newArg is no longer null, we need to build a new
1031+
// appendInterpolation(_:) call that takes it to replace the bad
1032+
// appendInterpolation(...) call.
1033+
if (newArg) {
1034+
auto newCallee = new (TC.Context) UnresolvedDotExpr(
1035+
callee->getBase(), /*dotloc=*/SourceLoc(),
1036+
DeclName(TC.Context.Id_appendInterpolation),
1037+
/*nameloc=*/DeclNameLoc(), /*Implicit=*/true);
1038+
1039+
E = CallExpr::create(TC.Context, newCallee, lParen,
1040+
{ newArg }, { Identifier() }, { SourceLoc() },
1041+
rParen, /*trailingClosure=*/nullptr, /*implicit=*/false);
1042+
}
1043+
}
1044+
}
1045+
1046+
// There is never a CallExpr between an InterpolatedStringLiteralExpr
1047+
// and an un-typechecked appendInterpolation(...) call, so whether we
1048+
// changed E or not, we don't need to recurse any deeper.
1049+
return { false, E };
1050+
}
1051+
};
1052+
1053+
ISLE->getAppendingExpr()->walk(StrangeInterpolationRewriter(TC));
1054+
}
1055+
9551056
public:
9561057
PreCheckExpression(TypeChecker &tc, DeclContext *dc, Expr *parent)
9571058
: TC(tc), DC(dc), ParentExpr(parent) {}
@@ -1086,6 +1187,9 @@ namespace {
10861187
return finish(false, nullptr);
10871188
}
10881189

1190+
if (auto *ISLE = dyn_cast<InterpolatedStringLiteralExpr>(expr))
1191+
correctInterpolationIfStrange(ISLE);
1192+
10891193
return finish(true, expr);
10901194
}
10911195

test/Parse/strange_interpolation.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,12 @@ print("[\(stringInterpolationSegment: x)]")
5454
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
5555
// expected-note@-3{{remove 'stringInterpolationSegment' label to keep current behavior}} {{11-39=}}
5656

57+
print("[ \(foo: "[\(bar: x)]") ]")
58+
// CHECK-NEXT: [ [1] ]
59+
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
60+
// expected-note@-3{{remove 'foo' label to keep current behavior}} {{12-17=}}
61+
// expected-warning@-4{{labeled interpolations will not be ignored in Swift 5}}
62+
// expected-note@-5{{remove 'bar' label to keep current behavior}} {{21-26=}}
63+
5764
print("End")
5865
// CHECK-NEXT: End
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Parse-only modes, like swift -parse and SourceKit, should not emit strange
2+
// interpolation errors in `#if swift(>=5)` blocks. (SR-9937)
3+
//
4+
// Even though this is in the test/SourceKit directory, it also tests
5+
// -frontend -parse behavior because the test cases are exactly the same.
6+
7+
// RUN: %sourcekitd-test -req=open %s -- -swift-version 4.2 %s == -req=print-diags %s | %FileCheck %s
8+
// RUN: %target-swift-frontend -parse -verify -swift-version 4.2 %s
9+
10+
let x = 1
11+
12+
// We should not warn in blocks that can only run when `swift(>=5.0)`.
13+
14+
#if swift(>=5.0)
15+
print("[\(foo: x)]")
16+
print("[\(x, x)]")
17+
#endif
18+
19+
// CHECK: <<NULL>>

0 commit comments

Comments
 (0)