Skip to content

Commit 82928fd

Browse files
authored
Merge pull request #24464 from brentdax/charmed-interpolations
[Parse][Sema] Improve interpolation parsing and construction
2 parents 7ef557e + 4f1e05c commit 82928fd

File tree

6 files changed

+221
-118
lines changed

6 files changed

+221
-118
lines changed

include/swift/AST/Expr.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2377,14 +2377,30 @@ class UnresolvedDotExpr : public Expr {
23772377
: FunctionRefKind::Unapplied);
23782378
}
23792379

2380-
SourceLoc getLoc() const { return NameLoc.getBaseNameLoc(); }
2380+
SourceLoc getLoc() const {
2381+
if (NameLoc.isValid())
2382+
return NameLoc.getBaseNameLoc();
2383+
else if (DotLoc.isValid())
2384+
return DotLoc;
2385+
else
2386+
return SubExpr->getEndLoc();
2387+
}
23812388

23822389
SourceLoc getStartLoc() const {
2383-
return (DotLoc.isInvalid() ? NameLoc.getSourceRange().End
2384-
: SubExpr->getStartLoc());
2390+
if (SubExpr->getStartLoc().isValid())
2391+
return SubExpr->getStartLoc();
2392+
else if (DotLoc.isValid())
2393+
return DotLoc;
2394+
else
2395+
return NameLoc.getSourceRange().Start;
23852396
}
23862397
SourceLoc getEndLoc() const {
2387-
return NameLoc.getSourceRange().End;
2398+
if (NameLoc.isValid())
2399+
return NameLoc.getSourceRange().End;
2400+
else if (DotLoc.isValid())
2401+
return DotLoc;
2402+
else
2403+
return SubExpr->getEndLoc();
23882404
}
23892405

23902406
SourceLoc getDotLoc() const { return DotLoc; }

lib/Parse/ParseExpr.cpp

Lines changed: 15 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,8 +1863,8 @@ parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
18631863
SyntaxKind::ExpressionSegment);
18641864

18651865
// Backslash is part of an expression segment.
1866-
Token BackSlash(tok::backslash,
1867-
CharSourceRange(Segment.Loc.getAdvancedLoc(-1), 1).str());
1866+
SourceLoc BackSlashLoc = Segment.Loc.getAdvancedLoc(-1);
1867+
Token BackSlash(tok::backslash, CharSourceRange(BackSlashLoc, 1).str());
18681868
ExprContext.addToken(BackSlash, EmptyTrivia, EmptyTrivia);
18691869
// Create a temporary lexer that lexes from the body of the string.
18701870
LexerState BeginState =
@@ -1888,30 +1888,12 @@ parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
18881888
TokReceiver->registerTokenKindChange(Tok.getLoc(),
18891889
tok::string_interpolation_anchor);
18901890

1891-
SourceLoc lParen, rParen;
1892-
SmallVector<Expr *, 4> args;
1893-
SmallVector<Identifier, 4> argLabels;
1894-
SmallVector<SourceLoc, 4> argLabelLocs;
1895-
Expr *trailingClosureNeverPresent;
1896-
ParserStatus S =
1897-
parseExprList(tok::l_paren, tok::r_paren,
1898-
/*isPostfix=*/false, /*isExprBasic=*/true,
1899-
lParen, args, argLabels, argLabelLocs, rParen,
1900-
trailingClosureNeverPresent,
1901-
SyntaxKind::FunctionCallArgumentList);
1902-
assert(!trailingClosureNeverPresent);
1903-
1904-
Status |= S;
1905-
// If there was an error parsing a parameter, add an ErrorExpr to
1906-
// represent it. Prevents spurious errors about a nonexistent
1907-
// appendInterpolation() overload.
1908-
if (S.isError() && args.size() == 0)
1909-
args.push_back(new (Context) ErrorExpr(SourceRange(lParen, rParen)));
1910-
1911-
while (argLabels.size() < args.size())
1912-
argLabels.push_back(Identifier());
1913-
while (argLabelLocs.size() < args.size())
1914-
argLabelLocs.push_back(SourceLoc());
1891+
auto callee = new (Context) UnresolvedDotExpr(InterpolationVarRef,
1892+
/*dotloc=*/BackSlashLoc,
1893+
appendInterpolation,
1894+
/*nameloc=*/DeclNameLoc(),
1895+
/*Implicit=*/true);
1896+
auto S = parseExprCallSuffix(makeParserResult(callee), true);
19151897

19161898
// If we stopped parsing the expression before the expression segment is
19171899
// over, eat the remaining tokens into a token list
@@ -1925,51 +1907,14 @@ parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
19251907
L->getLocForEndOfToken(SourceMgr, Tok.getLoc()));
19261908
}
19271909

1910+
Expr *call = S.getPtrOrNull();
1911+
if (!call)
1912+
call = new (Context) ErrorExpr(SourceRange(Segment.Loc,
1913+
Segment.getEndLoc()));
1914+
19281915
InterpolationCount += 1;
1929-
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-
1961-
auto AppendInterpolationRef =
1962-
new (Context) UnresolvedDotExpr(InterpolationVarRef,
1963-
/*dotloc=*/SourceLoc(),
1964-
appendInterpolation,
1965-
/*nameloc=*/DeclNameLoc(),
1966-
/*Implicit=*/true);
1967-
auto AppendInterpolationCall =
1968-
CallExpr::create(Context, AppendInterpolationRef,
1969-
lParen, args, argLabels, argLabelLocs, rParen,
1970-
/*trailingClosure=*/nullptr, /*implicit=*/false);
1971-
1972-
Stmts.push_back(AppendInterpolationCall);
1916+
Stmts.push_back(call);
1917+
Status |= S;
19731918

19741919
if (!Tok.is(tok::eof)) {
19751920
diagnose(Tok, diag::string_interpolation_extra);

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/IDE/complete_at_top_level.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ func fooFunc2(_ a: Int, _ b: Double) {}
167167

168168
func erroneous1(_ x: Undeclared) {}
169169

170+
// FIXME: Hides all other string interpolation completion.
171+
//extension DefaultStringInterpolation {
172+
// mutating func appendInterpolation(interpolate: Double) {}
173+
//}
174+
170175
//===--- Test code completions of expressions that can be typechecked.
171176

172177
// Although the parser can recover in most of these test cases, we resync it
@@ -465,6 +470,7 @@ func resyncParserB14() {}
465470
var stringInterp = "\(#^STRING_INTERP_3^#)"
466471
_ = "" + "\(#^STRING_INTERP_4^#)" + ""
467472
// STRING_INTERP: Begin completions
473+
// STRING_INTERP-DAG: Decl[InstanceMethod]/CurrNominal: ['(']{#(value): _#}[')'][#Void#]; name=value: _
468474
// STRING_INTERP-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#];
469475
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/NotRecommended/TypeRelation[Invalid]: fooFunc1()[#Void#];
470476
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule: optStr()[#String?#];

test/Parse/strange_interpolation.swift

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,60 @@
66

77
// REQUIRES: executable_test
88

9-
let x = 1
10-
11-
print("[\(x)]")
12-
// CHECK: [1]
13-
14-
print("[\(foo: x)]")
15-
// CHECK: [1]
16-
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
17-
// expected-note@-3{{remove 'foo' label to keep current behavior}} {{12-17=}}
18-
19-
print("[\(x, x)]")
20-
// CHECK: [(1, 1)]
21-
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
22-
// expected-note@-3{{insert parentheses to keep current behavior}} {{12-12=(}} {{16-16=)}}
23-
24-
print("[\(foo: x, x)]")
25-
// CHECK: [(foo: 1, 1)]
26-
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
27-
// expected-note@-3{{insert parentheses to keep current behavior}} {{12-12=(}} {{21-21=)}}
28-
29-
print("[\(x, foo: x)]")
30-
// CHECK: [(1, foo: 1)]
31-
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
32-
// expected-note@-3{{insert parentheses to keep current behavior}} {11-11(}} {{20-20=)}}
33-
34-
print("[\(foo: x, foo: x)]")
35-
// CHECK: [(foo: 1, foo: 1)]
36-
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
37-
// expected-note@-3{{insert parentheses to keep current behavior}} {11-11(}} {{25-25=)}}
38-
39-
print("[\(describing: x)]")
40-
// CHECK: [1]
41-
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
42-
// expected-note@-3{{remove 'describing' label to keep current behavior}} {{12-24=}}
43-
44-
print("[\(x, radix: x)]")
45-
// CHECK: [(1, radix: 1)]
46-
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
47-
// expected-note@-3{{insert parentheses to keep current behavior}} {11-11(}} {{25-25=)}}
48-
49-
print("[\(stringInterpolationSegment: x)]")
50-
// CHECK: [1]
51-
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
52-
// expected-note@-3{{remove 'stringInterpolationSegment' label to keep current behavior}} {{12-40=}}
9+
print("Begin")
10+
// CHECK: Begin
11+
12+
let x = 1
13+
14+
print("[\(x)]")
15+
// CHECK-NEXT: [1]
16+
17+
print("[\(foo: x)]")
18+
// CHECK-NEXT: [1]
19+
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
20+
// expected-note@-3{{remove 'foo' label to keep current behavior}} {{11-16=}}
21+
22+
print("[\(x, x)]")
23+
// CHECK-NEXT: [(1, 1)]
24+
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
25+
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{15-15=)}}
26+
27+
print("[\(foo: x, x)]")
28+
// CHECK-NEXT: [(foo: 1, 1)]
29+
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
30+
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{20-20=)}}
31+
32+
print("[\(x, foo: x)]")
33+
// CHECK-NEXT: [(1, foo: 1)]
34+
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
35+
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{20-20=)}}
36+
37+
print("[\(foo: x, foo: x)]")
38+
// CHECK-NEXT: [(foo: 1, foo: 1)]
39+
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
40+
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{25-25=)}}
41+
42+
print("[\(describing: x)]")
43+
// CHECK-NEXT: [1]
44+
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
45+
// expected-note@-3{{remove 'describing' label to keep current behavior}} {{11-23=}}
46+
47+
print("[\(x, radix: x)]")
48+
// CHECK-NEXT: [(1, radix: 1)]
49+
// expected-warning@-2{{interpolating multiple values will not form a tuple in Swift 5}}
50+
// expected-note@-3{{insert parentheses to keep current behavior}} {{11-11=(}} {{22-22=)}}
51+
52+
print("[\(stringInterpolationSegment: x)]")
53+
// CHECK-NEXT: [1]
54+
// expected-warning@-2{{labeled interpolations will not be ignored in Swift 5}}
55+
// expected-note@-3{{remove 'stringInterpolationSegment' label to keep current behavior}} {{11-39=}}
56+
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+
64+
print("End")
65+
// CHECK-NEXT: End

0 commit comments

Comments
 (0)