Skip to content

Commit 9bd1a26

Browse files
authored
Implementation for SE-0228: Fix ExpressibleByStringInterpolation (#20214)
* [CodeCompletion] Restrict ancestor search to brace This change allows ExprParentFinder to restrict certain searches for parents to just AST nodes within the nearest surrounding BraceStmt. In the string interpolation rework, BraceStmts can appear in new places in the AST; this keeps code completion from looking at irrelevant context. NFC in this commit, but keeps code completion from crashing once TapExpr is introduced. * Remove test relying on ExpressibleByStringInterpolation being deprecated Since soon enough, it won’t be anymore. * [AST] Introduce TapExpr TapExpr allows a block of code to to be inserted between two expressions, accessing and potentially mutating the result of its subexpression before giving it to its parent expression. It’s roughly equivalent to this function: func _tap<T>(_ value: T, do body: (inout T) throws -> Void) rethrows -> T { var copy = value try body(&copy) return copy } Except that it doesn’t use a closure, so no variables are captured and no call frame is (even notionally) added. This commit does not include tests because nothing in it actually uses TapExpr yet. It will be used by string interpolation. * SE-0228: Fix ExpressibleByStringInterpolation This is the bulk of the implementation of the string interpolation rework. It includes a redesigned AST node, new parsing logic, new constraints and post-typechecking code generation, and new standard library types and members. * [Sema] Rip out typeCheckExpressionShallow() With new string interpolation in place, it is no longer used by anything in the compiler. * [Sema] Diagnose invalid StringInterpolationProtocols StringInterpolationProtocol informally requires conforming types to provide at least one method with the base name “appendInterpolation” with no (or a discardable) return value and visibility at least as broad as the conforming type’s. This change diagnoses an error when a conforming type does not have a method that meets those criteria. * [Stdlib] Fix map(String.init) source break Some users, including some in the source compatibility suite, accidentally used init(stringInterpolationSegment:) by writing code like `map(String.init)`. Now that these intializers have been removed, the remaining initializers often end up tying during overload resolution. This change adds several overloads of `String.init(describing:)` which will break these ties in cases where the compiler previously selected `String.init(stringInterpolationSegment:)`. * [Sema] Make callWitness() take non-mutable arrays It doesn’t actually need to mutate them. * [Stdlib] Improve floating-point interpolation performance This change avoids constructing a String when interpolating a Float, Double, or Float80. Instead, we write the characters to a fixed-size buffer and then append them directly to the string’s storage. This seems to improve performance for all three types, but especially for Double and Float80, which cannot always fit into a small string when stringified. * [NameLookup] Improve MemberLookupTable invalidation In rare cases usually involving generated code, an overload added by an extension in the middle of a file would not be visible below it if the type had lazy members and the same base name had already been referenced above the extension. This change essentially dirties a type’s member lookup table whenever an extension is added to it, ensuring the entries in it will be updated. This change also includes some debugging improvements for NameLookup. * [SILOptimizer] XFAIL dead object removal failure The DeadObjectRemoval pass in SILOptimizer does not currently remove reworked string interpolations as well as the old design because their effects cannot be described by @_effects(readonly). That causes a test failure on Linux. This change temporarily silences that test. The SILOptimizer issue has been filed as SR-9008. * Confess string interpolation’s source stability sins * [Parser] Parse empty interpolations Previously, the parser had an odd asymmetry which caused the same function to accept foo(), but reject “\()”. This change fixes the issue. Already tested by test/Parse/try.swift, which uses this construct in one of its throwing interpolation tests. * [Sema] Fix batch-mode-only lazy var bug The temporary variable used by string interpolation needs to be recontextualized when it’s inserted into a synthesized getter. Fixes a compilation failure in Alamofire. I’ll probably follow up on this bug a bit more after merging.
1 parent 746fb28 commit 9bd1a26

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2001
-568
lines changed

docs/Literals.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ to jump through.
136136
The default array literal type is always Array, and the default dictionary
137137
literal type is always Dictionary.
138138

139-
String interpolations are a bit different: they try to individually convert
140-
each element of the interpolation to the type that adopts
141-
_ExpressibleByStringInterpolation, then calls the variadic
142-
``convertFromStringInterpolation`` to put them all together. The default type
139+
String interpolations are a bit different: they create an instance of
140+
``T.StringInterpolation`` and append each segment to it, then initialize
141+
an instance of ``T`` with that instance. The default type
143142
for an interpolated literal without context is also ``StringLiteralType``.

include/swift/AST/Decl.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3051,16 +3051,26 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
30513051
/// The table itself is lazily constructed and updated when
30523052
/// lookupDirect() is called. The bit indicates whether the lookup
30533053
/// table has already added members by walking the declarations in
3054-
/// scope.
3054+
/// scope; it should be manipulated through \c isLookupTablePopulated()
3055+
/// and \c setLookupTablePopulated().
30553056
llvm::PointerIntPair<MemberLookupTable *, 1, bool> LookupTable;
30563057

30573058
/// Prepare the lookup table to make it ready for lookups.
30583059
void prepareLookupTable(bool ignoreNewExtensions);
30593060

3061+
/// True if the entries in \c LookupTable are complete--that is, if a
3062+
/// name is present, it contains all members with that name.
3063+
bool isLookupTablePopulated() const;
3064+
void setLookupTablePopulated(bool value);
3065+
30603066
/// Note that we have added a member into the iterable declaration context,
30613067
/// so that it can also be added to the lookup table (if needed).
30623068
void addedMember(Decl *member);
30633069

3070+
/// Note that we have added an extension into the nominal type,
3071+
/// so that its members can eventually be added to the lookup table.
3072+
void addedExtension(ExtensionDecl *ext);
3073+
30643074
/// \brief A lookup table used to find the protocol conformances of
30653075
/// a given nominal type.
30663076
mutable ConformanceLookupTable *ConformanceTable = nullptr;

include/swift/AST/DiagnosticsParse.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,16 @@ ERROR(expected_type_after_as,none,
12211221
ERROR(string_interpolation_extra,none,
12221222
"extra tokens after interpolated string expression", ())
12231223

1224+
// Interpolations with parameter labels or multiple values
1225+
WARNING(string_interpolation_list_changing,none,
1226+
"interpolating multiple values will not form a tuple in Swift 5", ())
1227+
NOTE(string_interpolation_list_insert_parens,none,
1228+
"insert parentheses to keep current behavior", ())
1229+
WARNING(string_interpolation_label_changing,none,
1230+
"labeled interpolations will not be ignored in Swift 5", ())
1231+
NOTE(string_interpolation_remove_label,none,
1232+
"remove %0 label to keep current behavior", (Identifier))
1233+
12241234
// Keypath expressions.
12251235
ERROR(expr_keypath_expected_lparen,PointsToFirstBadToken,
12261236
"expected '(' following '#keyPath'", ())

include/swift/AST/DiagnosticsSema.def

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,22 @@ NOTE(req_near_match_access,none,
19041904
"make %0 %select{ERROR|private|private|non-public|non-public}1 to silence this "
19051905
"warning", (DeclName, AccessLevel))
19061906

1907+
// appendInterpolation methods
1908+
ERROR(missing_append_interpolation,none,
1909+
"type conforming to 'StringInterpolationProtocol' does not implement "
1910+
"a valid 'appendInterpolation' method", ())
1911+
WARNING(append_interpolation_static,none,
1912+
"'appendInterpolation' method will never be used because it is static",
1913+
())
1914+
WARNING(append_interpolation_void_or_discardable,none,
1915+
"'appendInterpolation' method does not return 'Void' or have a "
1916+
"discardable result", ())
1917+
WARNING(append_interpolation_access_control,none,
1918+
"'appendInterpolation' method is %select{private|fileprivate|internal|"
1919+
"public|open}0, but %1 is %select{private|fileprivate|internal|public|"
1920+
"open}2",
1921+
(AccessLevel, DeclName, AccessLevel))
1922+
19071923
// Protocols and existentials
19081924
ERROR(assoc_type_outside_of_protocol,none,
19091925
"associated type %0 can only be used with a concrete type or "
@@ -2747,10 +2763,10 @@ ERROR(optional_used_as_boolean,none,
27472763
"test for '!= nil' instead", (Type))
27482764

27492765
ERROR(interpolation_missing_proto,none,
2750-
"string interpolation requires the protocol '_ExpressibleByStringInterpolation' to be defined",
2766+
"string interpolation requires the protocol 'ExpressibleByStringInterpolation' to be defined",
27512767
())
27522768
ERROR(interpolation_broken_proto,none,
2753-
"protocol '_ExpressibleByStringInterpolation' is broken",
2769+
"protocol 'ExpressibleByStringInterpolation' is broken",
27542770
())
27552771

27562772
ERROR(object_literal_broken_proto,none,
@@ -3362,6 +3378,8 @@ ERROR(throw_in_illegal_context,none,
33623378

33633379
ERROR(throwing_operator_without_try,none,
33643380
"operator can throw but expression is not marked with 'try'", ())
3381+
ERROR(throwing_interpolation_without_try,none,
3382+
"interpolation can throw but is not marked with 'try'", ())
33653383
ERROR(throwing_call_without_try,none,
33663384
"call can throw but is not marked with 'try'", ())
33673385
NOTE(note_forgot_try,none,

include/swift/AST/Expr.h

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ namespace swift {
5757
class PatternBindingDecl;
5858
class ParameterList;
5959
class EnumElementDecl;
60+
class CallExpr;
6061

6162
enum class ExprKind : uint8_t {
6263
#define EXPR(Id, Parent) Id,
@@ -171,6 +172,12 @@ class alignas(8) Expr {
171172
IsSingleExtendedGraphemeCluster : 1
172173
);
173174

175+
SWIFT_INLINE_BITFIELD_FULL(InterpolatedStringLiteralExpr, LiteralExpr, 32+20,
176+
: NumPadBits,
177+
InterpolationCount : 20,
178+
LiteralCapacity : 32
179+
);
180+
174181
SWIFT_INLINE_BITFIELD(DeclRefExpr, Expr, 2+2,
175182
Semantics : 2, // an AccessSemantics
176183
FunctionRefKind : 2
@@ -916,6 +923,42 @@ class StringLiteralExpr : public LiteralExpr {
916923
}
917924
};
918925

926+
/// \brief Runs a series of statements which use or modify \c SubExpr
927+
/// before it is given to the rest of the expression.
928+
///
929+
/// \c Body should begin with a \c VarDecl; this defines the variable
930+
/// \c TapExpr will initialize at the beginning and read a result
931+
/// from at the end. \c TapExpr creates a separate scope, then
932+
/// assigns the result of \c SubExpr to the variable and runs \c Body
933+
/// in it, returning the value of the variable after the \c Body runs.
934+
///
935+
/// (The design here could be a bit cleaner, particularly where the VarDecl
936+
/// is concerned.)
937+
class TapExpr : public Expr {
938+
Expr *SubExpr;
939+
BraceStmt *Body;
940+
941+
public:
942+
TapExpr(Expr *SubExpr, BraceStmt *Body);
943+
944+
Expr * getSubExpr() const { return SubExpr; }
945+
void setSubExpr(Expr * se) { SubExpr = se; }
946+
947+
/// \brief The variable which will be accessed and possibly modified by
948+
/// the \c Body. This is the first \c ASTNode in the \c Body.
949+
VarDecl * getVar() const;
950+
951+
BraceStmt * getBody() const { return Body; }
952+
void setBody(BraceStmt * b) { Body = b; }
953+
954+
SourceLoc getLoc() const { return SourceLoc(); }
955+
SourceRange getSourceRange() const { return SourceRange(); }
956+
957+
static bool classof(const Expr *E) {
958+
return E->getKind() == ExprKind::Tap;
959+
}
960+
};
961+
919962
/// InterpolatedStringLiteral - An interpolated string literal.
920963
///
921964
/// An interpolated string literal mixes expressions (which are evaluated and
@@ -927,16 +970,37 @@ class StringLiteralExpr : public LiteralExpr {
927970
class InterpolatedStringLiteralExpr : public LiteralExpr {
928971
/// Points at the beginning quote.
929972
SourceLoc Loc;
930-
MutableArrayRef<Expr *> Segments;
973+
TapExpr *AppendingExpr;
931974
Expr *SemanticExpr;
932975

933976
public:
934-
InterpolatedStringLiteralExpr(SourceLoc Loc, MutableArrayRef<Expr *> Segments)
935-
: LiteralExpr(ExprKind::InterpolatedStringLiteral, /*Implicit=*/false),
936-
Loc(Loc), Segments(Segments), SemanticExpr() { }
937-
938-
MutableArrayRef<Expr *> getSegments() { return Segments; }
939-
ArrayRef<Expr *> getSegments() const { return Segments; }
977+
InterpolatedStringLiteralExpr(SourceLoc Loc, unsigned LiteralCapacity,
978+
unsigned InterpolationCount,
979+
TapExpr *AppendingExpr)
980+
: LiteralExpr(ExprKind::InterpolatedStringLiteral, /*Implicit=*/false),
981+
Loc(Loc), AppendingExpr(AppendingExpr), SemanticExpr() {
982+
Bits.InterpolatedStringLiteralExpr.InterpolationCount = InterpolationCount;
983+
Bits.InterpolatedStringLiteralExpr.LiteralCapacity = LiteralCapacity;
984+
}
985+
986+
/// \brief Retrieve the value of the literalCapacity parameter to the
987+
/// initializer.
988+
unsigned getLiteralCapacity() const {
989+
return Bits.InterpolatedStringLiteralExpr.LiteralCapacity;
990+
}
991+
992+
/// \brief Retrieve the value of the interpolationCount parameter to the
993+
/// initializer.
994+
unsigned getInterpolationCount() const {
995+
return Bits.InterpolatedStringLiteralExpr.InterpolationCount;
996+
}
997+
998+
/// \brief A block containing expressions which call
999+
/// \c StringInterpolationProtocol methods to append segments to the
1000+
/// string interpolation. The first node in \c Body should be an uninitialized
1001+
/// \c VarDecl; the other statements should append to it.
1002+
TapExpr * getAppendingExpr() const { return AppendingExpr; }
1003+
void setAppendingExpr(TapExpr * AE) { AppendingExpr = AE; }
9401004

9411005
/// \brief Retrieve the expression that actually evaluates the resulting
9421006
/// string, typically with a series of '+' operations.
@@ -951,6 +1015,10 @@ class InterpolatedStringLiteralExpr : public LiteralExpr {
9511015
// token, so the range should be (Start == End).
9521016
return Loc;
9531017
}
1018+
1019+
/// \brief Call the \c callback with information about each segment in turn.
1020+
void forEachSegment(ASTContext &Ctx,
1021+
llvm::function_ref<void(bool, CallExpr *)> callback);
9541022

9551023
static bool classof(const Expr *E) {
9561024
return E->getKind() == ExprKind::InterpolatedStringLiteral;

include/swift/AST/ExprNodes.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ EXPR(EditorPlaceholder, Expr)
188188
EXPR(ObjCSelector, Expr)
189189
EXPR(KeyPath, Expr)
190190
UNCHECKED_EXPR(KeyPathDot, Expr)
191-
LAST_EXPR(KeyPathDot)
191+
EXPR(Tap, Expr)
192+
LAST_EXPR(Tap)
192193

193194
#undef EXPR_RANGE
194195
#undef LITERAL_EXPR

include/swift/AST/KnownIdentifiers.def

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,12 @@ IDENTIFIER_(builtinUTF16StringLiteral)
165165
IDENTIFIER_(builtinStringLiteral)
166166
IDENTIFIER(StringLiteralType)
167167
IDENTIFIER(stringInterpolation)
168-
IDENTIFIER(stringInterpolationSegment)
168+
IDENTIFIER(StringInterpolation)
169+
IDENTIFIER(literalCapacity)
170+
IDENTIFIER(interpolationCount)
171+
IDENTIFIER(appendLiteral)
172+
IDENTIFIER(appendInterpolation)
173+
IDENTIFIER_WITH_NAME(dollarInterpolation, "$interpolation")
169174
IDENTIFIER(arrayLiteral)
170175
IDENTIFIER(dictionaryLiteral)
171176
IDENTIFIER_(getBuiltinLogicValue)

include/swift/AST/KnownProtocols.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ PROTOCOL(Decodable)
7070
PROTOCOL_(ObjectiveCBridgeable)
7171
PROTOCOL_(DestructorSafeContainer)
7272

73+
PROTOCOL(StringInterpolationProtocol)
74+
7375
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByArrayLiteral)
7476
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByBooleanLiteral)
7577
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByDictionaryLiteral)
7678
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByExtendedGraphemeClusterLiteral)
7779
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByFloatLiteral)
7880
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByIntegerLiteral)
79-
EXPRESSIBLE_BY_LITERAL_PROTOCOL_(ExpressibleByStringInterpolation)
81+
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByStringInterpolation)
8082
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByStringLiteral)
8183
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByNilLiteral)
8284
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByUnicodeScalarLiteral)

include/swift/Parse/Parser.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,8 +1219,11 @@ class Parser {
12191219
StringRef copyAndStripUnderscores(StringRef text);
12201220

12211221
ParserStatus parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
1222-
SmallVectorImpl<Expr*> &Exprs,
1223-
Token EntireTok);
1222+
Token EntireTok,
1223+
VarDecl *InterpolationVar,
1224+
SmallVectorImpl<ASTNode> &Stmts,
1225+
unsigned &LiteralCapacity,
1226+
unsigned &InterpolationCount);
12241227

12251228
/// Parse an argument label `identifier ':'`, if it exists.
12261229
///

lib/AST/ASTDumper.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,10 +1839,10 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
18391839
}
18401840
void visitInterpolatedStringLiteralExpr(InterpolatedStringLiteralExpr *E) {
18411841
printCommon(E, "interpolated_string_literal_expr");
1842-
for (auto Segment : E->getSegments()) {
1843-
OS << '\n';
1844-
printRec(Segment);
1845-
}
1842+
PrintWithColorRAII(OS, LiteralValueColor) << " literal_capacity="
1843+
<< E->getLiteralCapacity() << " interpolation_count="
1844+
<< E->getInterpolationCount() << '\n';
1845+
printRec(E->getAppendingExpr());
18461846
printSemanticExpr(E->getSemanticExpr());
18471847
PrintWithColorRAII(OS, ParenthesisColor) << ')';
18481848
}
@@ -2644,6 +2644,19 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
26442644
printCommon(E, "key_path_dot_expr");
26452645
PrintWithColorRAII(OS, ParenthesisColor) << ')';
26462646
}
2647+
2648+
void visitTapExpr(TapExpr *E) {
2649+
printCommon(E, "tap_expr");
2650+
PrintWithColorRAII(OS, DeclColor) << " var=";
2651+
printDeclRef(E->getVar());
2652+
OS << '\n';
2653+
2654+
printRec(E->getSubExpr());
2655+
OS << '\n';
2656+
2657+
printRec(E->getBody(), E->getVar()->getDeclContext()->getASTContext());
2658+
PrintWithColorRAII(OS, ParenthesisColor) << ')';
2659+
}
26472660
};
26482661

26492662
} // end anonymous namespace

lib/AST/ASTWalker.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,9 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
446446
Expr *visitInterpolatedStringLiteralExpr(InterpolatedStringLiteralExpr *E) {
447447
HANDLE_SEMANTIC_EXPR(E);
448448

449-
for (auto &Segment : E->getSegments()) {
450-
if (Expr *Seg = doIt(Segment))
451-
Segment = Seg;
449+
if (auto oldAppendingExpr = E->getAppendingExpr()) {
450+
if (auto appendingExpr = doIt(oldAppendingExpr))
451+
E->setAppendingExpr(dyn_cast<TapExpr>(appendingExpr));
452452
else
453453
return nullptr;
454454
}
@@ -1050,6 +1050,28 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
10501050

10511051
Expr *visitKeyPathDotExpr(KeyPathDotExpr *E) { return E; }
10521052

1053+
Expr *visitTapExpr(TapExpr *E) {
1054+
if (auto oldSubExpr = E->getSubExpr()) {
1055+
if (auto subExpr = doIt(oldSubExpr)) {
1056+
E->setSubExpr(subExpr);
1057+
}
1058+
else {
1059+
return nullptr;
1060+
}
1061+
}
1062+
1063+
if (auto oldBody = E->getBody()) {
1064+
if (auto body = doIt(oldBody)) {
1065+
E->setBody(dyn_cast<BraceStmt>(body));
1066+
}
1067+
else {
1068+
return nullptr;
1069+
}
1070+
}
1071+
1072+
return E;
1073+
}
1074+
10531075
//===--------------------------------------------------------------------===//
10541076
// Everything Else
10551077
//===--------------------------------------------------------------------===//

lib/AST/Decl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,6 +2995,8 @@ void NominalTypeDecl::addExtension(ExtensionDecl *extension) {
29952995
// Add to the end of the list.
29962996
LastExtension->NextExtension.setPointer(extension);
29972997
LastExtension = extension;
2998+
2999+
addedExtension(extension);
29983000
}
29993001

30003002
auto NominalTypeDecl::getStoredProperties(bool skipInaccessible) const

0 commit comments

Comments
 (0)