Skip to content

Implementation for SE-0228: Fix ExpressibleByStringInterpolation #19963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7aefd79
Tweak TypeChecker::callWitness() signature
beccadax Aug 8, 2018
e8a433a
Character->String through a less circular path
beccadax Aug 8, 2018
4a6765f
Extract NSCoding unstable name diagnosis
beccadax Aug 8, 2018
42bdeb1
Add TapExpr
beccadax Aug 9, 2018
206f7bc
Permit empty interpolations
beccadax Aug 9, 2018
b30b7b7
Refactor getCalledValue()
beccadax Aug 9, 2018
30936d6
Rework ExpressibleByStringInterpolation
beccadax Aug 9, 2018
eef88ec
Rip out typeCheckExpressionShallow()
beccadax Aug 9, 2018
d4d9b4b
Tweak fix-it locations for throwing interpolators
beccadax Aug 9, 2018
8d8b484
Diagnose missing appendInterpolation methods
beccadax Aug 9, 2018
ccdaa86
Warn about strange interpolations in Swift 4.2
beccadax Aug 9, 2018
b77d177
Late-breaking string interpolation test fixes
beccadax Aug 9, 2018
d0c9578
Conform float types to TextOutputStreamable
beccadax Oct 25, 2018
b238c8c
Remove @_effects(readonly) helper functions
beccadax Aug 15, 2018
8d9e695
Apply feedback from @slavapestov
beccadax Aug 16, 2018
3f69fe1
Simplify InterpolatedStringLiteralExpr::forEachSegment()
beccadax Aug 16, 2018
2f63a7d
Fix issues from rebasing
beccadax Aug 17, 2018
3688673
Update for DeclContext renaming
beccadax Aug 19, 2018
5453ed2
Fix method call on null object
beccadax Aug 29, 2018
bd67d88
Make both same-file extensions and default args work
beccadax Sep 2, 2018
4289e91
Improve debuggability of lookupDirect()
beccadax Sep 10, 2018
6edf5ab
Remove dead MemberLookupTable::addExtensionMembers()
beccadax Sep 10, 2018
84f6bbf
Update lazy LookupTable when extension added
beccadax Sep 10, 2018
dcf8c36
Fix Linux-only build failure
beccadax Sep 11, 2018
5721732
Benchmark interpolation wtih custom types
beccadax Sep 14, 2018
a846c1a
Correct arguable CustomStringInterpolation benchmark flaw
beccadax Sep 17, 2018
d0fda51
Add missing @inlinable annotation
beccadax Sep 15, 2018
9060b9c
Test inlining an empty-string check
beccadax Sep 17, 2018
13c002b
Inline the SmallString check for reserveCapacity()
beccadax Sep 17, 2018
2cb546b
Update for function type changes
beccadax Oct 9, 2018
a7732fa
Update sil_location test for updated mangling
beccadax Oct 10, 2018
47a1060
Change suspect code completion test
beccadax Oct 10, 2018
5f375c4
Use multiline string literals in examples
beccadax Oct 13, 2018
6b9184e
Remove inaccurate comments
beccadax Oct 13, 2018
d6a265a
Improve "informal requirement" comment
beccadax Oct 13, 2018
6e690a2
Test mixture of throwing and nonthrowing interpolations
beccadax Oct 13, 2018
a124952
Style fixes
beccadax Oct 13, 2018
a9d6d2d
Delete ExpressibleByStringInterpolation deprecation test
beccadax Oct 13, 2018
2acf62b
XFAIL Linux-only test failure
beccadax Oct 15, 2018
3a2297d
Tweak constraints to improve performance
beccadax Oct 20, 2018
435880c
Remove new string interpolation benchmarks
beccadax Oct 22, 2018
4b31761
Inline only part of the append(_:) length check
beccadax Oct 25, 2018
ed44e2f
Create interpolation strings with appropriate initial capacity
beccadax Oct 27, 2018
b52f807
Give DefaultStringInterpolation its own append paths
beccadax Oct 28, 2018
94260a9
Confess my (current) source and ABI stability sins
beccadax Nov 1, 2018
f23d1cd
Skip += fast path for literal segments, too
beccadax Oct 29, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions benchmark/single-source/StringInterpolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ class RefTypePrintable : CustomStringConvertible {
}
}

struct CustomString: ExpressibleByStringInterpolation {
var value: String

init(stringLiteral: String) {
self.value = stringLiteral
}
}

@inline(never)
public func run_StringInterpolation(_ N: Int) {
let reps = 100
Expand Down
7 changes: 3 additions & 4 deletions docs/Literals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ to jump through.
The default array literal type is always Array, and the default dictionary
literal type is always Dictionary.

String interpolations are a bit different: they try to individually convert
each element of the interpolation to the type that adopts
_ExpressibleByStringInterpolation, then calls the variadic
``convertFromStringInterpolation`` to put them all together. The default type
String interpolations are a bit different: they create an instance of
``T.StringInterpolation`` and append each segment to it, then initialize
an instance of ``T`` with that instance. The default type
for an interpolated literal without context is also ``StringLiteralType``.
12 changes: 11 additions & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3051,16 +3051,26 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
/// The table itself is lazily constructed and updated when
/// lookupDirect() is called. The bit indicates whether the lookup
/// table has already added members by walking the declarations in
/// scope.
/// scope; it should be manipulated through \c isLookupTablePopulated()
/// and \c setLookupTablePopulated().
llvm::PointerIntPair<MemberLookupTable *, 1, bool> LookupTable;

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

/// True if the entries in \c LookupTable are complete--that is, if a
/// name is present, it contains all members with that name.
bool isLookupTablePopulated() const;
void setLookupTablePopulated(bool value);

/// Note that we have added a member into the iterable declaration context,
/// so that it can also be added to the lookup table (if needed).
void addedMember(Decl *member);

/// Note that we have added an extension into the nominal type,
/// so that its members can eventually be added to the lookup table.
void addedExtension(ExtensionDecl *ext);

/// \brief A lookup table used to find the protocol conformances of
/// a given nominal type.
mutable ConformanceLookupTable *ConformanceTable = nullptr;
Expand Down
10 changes: 10 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,16 @@ ERROR(expected_type_after_as,none,
ERROR(string_interpolation_extra,none,
"extra tokens after interpolated string expression", ())

// Interpolations with parameter labels or multiple values
WARNING(string_interpolation_list_changing,none,
"interpolating multiple values will not form a tuple in Swift 5", ())
NOTE(string_interpolation_list_insert_parens,none,
"insert parentheses to keep current behavior", ())
WARNING(string_interpolation_label_changing,none,
"labeled interpolations will not be ignored in Swift 5", ())
NOTE(string_interpolation_remove_label,none,
"remove %0 label to keep current behavior", (Identifier))

// Keypath expressions.
ERROR(expr_keypath_expected_lparen,PointsToFirstBadToken,
"expected '(' following '#keyPath'", ())
Expand Down
20 changes: 18 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1904,6 +1904,20 @@ NOTE(req_near_match_access,none,
"make %0 %select{ERROR|private|private|non-public|non-public}1 to silence this "
"warning", (DeclName, AccessLevel))

// appendInterpolation methods
ERROR(missing_append_interpolation,none,
"type conforming to 'StringInterpolationProtocol' does not implement "
"a valid 'appendInterpolation' method", ())
WARNING(append_interpolation_static,none,
"'appendInterpolation' method will never be used because it is static", ())
WARNING(append_interpolation_void_or_discardable,none,
"'appendInterpolation' method does not return 'Void' or have a "
"discardable result", ())
WARNING(append_interpolation_access_control,none,
"'appendInterpolation' method is %select{private|fileprivate|internal|public|open}0, "
"but %1 is %select{private|fileprivate|internal|public|open}2",
(AccessLevel, DeclName, AccessLevel))

// Protocols and existentials
ERROR(assoc_type_outside_of_protocol,none,
"associated type %0 can only be used with a concrete type or "
Expand Down Expand Up @@ -2749,10 +2763,10 @@ ERROR(optional_used_as_boolean,none,
"test for '!= nil' instead", (Type))

ERROR(interpolation_missing_proto,none,
"string interpolation requires the protocol '_ExpressibleByStringInterpolation' to be defined",
"string interpolation requires the protocol 'ExpressibleByStringInterpolation' to be defined",
())
ERROR(interpolation_broken_proto,none,
"protocol '_ExpressibleByStringInterpolation' is broken",
"protocol 'ExpressibleByStringInterpolation' is broken",
())

ERROR(object_literal_broken_proto,none,
Expand Down Expand Up @@ -3364,6 +3378,8 @@ ERROR(throw_in_illegal_context,none,

ERROR(throwing_operator_without_try,none,
"operator can throw but expression is not marked with 'try'", ())
ERROR(throwing_interpolation_without_try,none,
"interpolation can throw but is not marked with 'try'", ())
ERROR(throwing_call_without_try,none,
"call can throw but is not marked with 'try'", ())
NOTE(note_forgot_try,none,
Expand Down
80 changes: 73 additions & 7 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ namespace swift {
class PatternBindingDecl;
class ParameterList;
class EnumElementDecl;
class CallExpr;

enum class ExprKind : uint8_t {
#define EXPR(Id, Parent) Id,
Expand Down Expand Up @@ -171,6 +172,12 @@ class alignas(8) Expr {
IsSingleExtendedGraphemeCluster : 1
);

SWIFT_INLINE_BITFIELD_FULL(InterpolatedStringLiteralExpr, LiteralExpr, 32+20,
: NumPadBits,
InterpolationCount : 20,
LiteralCapacity : 32
);

SWIFT_INLINE_BITFIELD(DeclRefExpr, Expr, 2+2,
Semantics : 2, // an AccessSemantics
FunctionRefKind : 2
Expand Down Expand Up @@ -916,6 +923,41 @@ class StringLiteralExpr : public LiteralExpr {
}
};

/// \brief Runs a series of statements which use or modify \c SubExpr
/// before it is given to the rest of the expression.
///
/// \c Body should begin with a \c VarDecl; this defines the variable
/// \c TapExpr will initialize at the beginning and read a result
/// from at the end. \c TapExpr creates a separate scope, then
/// assigns the result of \c SubExpr to the variable and runs \c Body
/// in it, returning the value of the variable after the \c Body runs.
///
/// Note: This is a minimal, preliminary design. Expect it to change.
class TapExpr : public Expr {
Expr *SubExpr;
BraceStmt *Body;

public:
TapExpr(Expr *SubExpr, BraceStmt *Body);

Expr * getSubExpr() const { return SubExpr; }
void setSubExpr(Expr * se) { SubExpr = se; }

/// \brief The variable which will be accessed and possibly modified by
/// the \c Body. This is the first \c ASTNode in the \c Body.
VarDecl * getVar() const;

BraceStmt * getBody() const { return Body; }
void setBody(BraceStmt * b) { Body = b; }

SourceLoc getLoc() const { return SourceLoc(); }
SourceRange getSourceRange() const { return SourceRange(); }

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::Tap;
}
};

/// InterpolatedStringLiteral - An interpolated string literal.
///
/// An interpolated string literal mixes expressions (which are evaluated and
Expand All @@ -927,16 +969,37 @@ class StringLiteralExpr : public LiteralExpr {
class InterpolatedStringLiteralExpr : public LiteralExpr {
/// Points at the beginning quote.
SourceLoc Loc;
MutableArrayRef<Expr *> Segments;
TapExpr *AppendingExpr;
Expr *SemanticExpr;

public:
InterpolatedStringLiteralExpr(SourceLoc Loc, MutableArrayRef<Expr *> Segments)
: LiteralExpr(ExprKind::InterpolatedStringLiteral, /*Implicit=*/false),
Loc(Loc), Segments(Segments), SemanticExpr() { }

MutableArrayRef<Expr *> getSegments() { return Segments; }
ArrayRef<Expr *> getSegments() const { return Segments; }
InterpolatedStringLiteralExpr(SourceLoc Loc, unsigned LiteralCapacity,
unsigned InterpolationCount,
TapExpr *AppendingExpr)
: LiteralExpr(ExprKind::InterpolatedStringLiteral, /*Implicit=*/false),
Loc(Loc), AppendingExpr(AppendingExpr), SemanticExpr() {
Bits.InterpolatedStringLiteralExpr.InterpolationCount = InterpolationCount;
Bits.InterpolatedStringLiteralExpr.LiteralCapacity = LiteralCapacity;
}

/// \brief Retrieve the value of the literalCapacity parameter to the
/// initializer.
unsigned getLiteralCapacity() const {
return Bits.InterpolatedStringLiteralExpr.LiteralCapacity;
}

/// \brief Retrieve the value of the interpolationCount parameter to the
/// initializer.
unsigned getInterpolationCount() const {
return Bits.InterpolatedStringLiteralExpr.InterpolationCount;
}

/// \brief A block containing expressions which call
/// \c StringInterpolationProtocol methods to append segments to the
/// string interpolation. The first node in \c Body should be an uninitialized
/// \c VarDecl; the other statements should append to it.
TapExpr * getAppendingExpr() const { return AppendingExpr; }
void setAppendingExpr(TapExpr * AE) { AppendingExpr = AE; }

/// \brief Retrieve the expression that actually evaluates the resulting
/// string, typically with a series of '+' operations.
Expand All @@ -951,6 +1014,9 @@ class InterpolatedStringLiteralExpr : public LiteralExpr {
// token, so the range should be (Start == End).
return Loc;
}

/// \brief Call the \c callback with information about each segment in turn.
void forEachSegment(ASTContext &Ctx, llvm::function_ref<void(bool, CallExpr *)> callback);

static bool classof(const Expr *E) {
return E->getKind() == ExprKind::InterpolatedStringLiteral;
Expand Down
3 changes: 2 additions & 1 deletion include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ EXPR(EditorPlaceholder, Expr)
EXPR(ObjCSelector, Expr)
EXPR(KeyPath, Expr)
UNCHECKED_EXPR(KeyPathDot, Expr)
LAST_EXPR(KeyPathDot)
EXPR(Tap, Expr)
LAST_EXPR(Tap)

#undef EXPR_RANGE
#undef LITERAL_EXPR
Expand Down
7 changes: 6 additions & 1 deletion include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ IDENTIFIER_(builtinUTF16StringLiteral)
IDENTIFIER_(builtinStringLiteral)
IDENTIFIER(StringLiteralType)
IDENTIFIER(stringInterpolation)
IDENTIFIER(stringInterpolationSegment)
IDENTIFIER(StringInterpolation)
IDENTIFIER(literalCapacity)
IDENTIFIER(interpolationCount)
IDENTIFIER(appendLiteral)
IDENTIFIER(appendInterpolation)
IDENTIFIER_WITH_NAME(dollarInterpolation, "$interpolation")
IDENTIFIER(arrayLiteral)
IDENTIFIER(dictionaryLiteral)
IDENTIFIER_(getBuiltinLogicValue)
Expand Down
4 changes: 3 additions & 1 deletion include/swift/AST/KnownProtocols.def
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ PROTOCOL(Decodable)
PROTOCOL_(ObjectiveCBridgeable)
PROTOCOL_(DestructorSafeContainer)

PROTOCOL(StringInterpolationProtocol)

EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByArrayLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByBooleanLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByDictionaryLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByExtendedGraphemeClusterLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByFloatLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByIntegerLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL_(ExpressibleByStringInterpolation)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByStringInterpolation)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByStringLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByNilLiteral)
EXPRESSIBLE_BY_LITERAL_PROTOCOL(ExpressibleByUnicodeScalarLiteral)
Expand Down
7 changes: 5 additions & 2 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1219,8 +1219,11 @@ class Parser {
StringRef copyAndStripUnderscores(StringRef text);

ParserStatus parseStringSegments(SmallVectorImpl<Lexer::StringSegment> &Segments,
SmallVectorImpl<Expr*> &Exprs,
Token EntireTok);
Token EntireTok,
VarDecl *InterpolationVar,
SmallVectorImpl<ASTNode> &Stmts,
unsigned &LiteralCapacity,
unsigned &InterpolationCount);

/// Parse an argument label `identifier ':'`, if it exists.
///
Expand Down
21 changes: 17 additions & 4 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1839,10 +1839,10 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
}
void visitInterpolatedStringLiteralExpr(InterpolatedStringLiteralExpr *E) {
printCommon(E, "interpolated_string_literal_expr");
for (auto Segment : E->getSegments()) {
OS << '\n';
printRec(Segment);
}
PrintWithColorRAII(OS, LiteralValueColor) << " literal_capacity="
<< E->getLiteralCapacity() << " interpolation_count="
<< E->getInterpolationCount() << '\n';
printRec(E->getAppendingExpr());
printSemanticExpr(E->getSemanticExpr());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
Expand Down Expand Up @@ -2644,6 +2644,19 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
printCommon(E, "key_path_dot_expr");
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}

void visitTapExpr(TapExpr *E) {
printCommon(E, "tap_expr");
PrintWithColorRAII(OS, DeclColor) << " var=";
printDeclRef(E->getVar());
OS << '\n';

printRec(E->getSubExpr());
OS << '\n';

printRec(E->getBody(), E->getVar()->getDeclContext()->getASTContext());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
};

} // end anonymous namespace
Expand Down
28 changes: 25 additions & 3 deletions lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,9 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
Expr *visitInterpolatedStringLiteralExpr(InterpolatedStringLiteralExpr *E) {
HANDLE_SEMANTIC_EXPR(E);

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

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

Expr *visitTapExpr(TapExpr *E) {
if (auto oldSubExpr = E->getSubExpr()) {
if (auto subExpr = doIt(oldSubExpr)) {
E->setSubExpr(subExpr);
}
else {
return nullptr;
}
}

if (auto oldBody = E->getBody()) {
if (auto body = doIt(oldBody)) {
E->setBody(dyn_cast<BraceStmt>(body));
}
else {
return nullptr;
}
}

return E;
}

//===--------------------------------------------------------------------===//
// Everything Else
//===--------------------------------------------------------------------===//
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3000,6 +3000,8 @@ void NominalTypeDecl::addExtension(ExtensionDecl *extension) {
// Add to the end of the list.
LastExtension->NextExtension.setPointer(extension);
LastExtension = extension;

addedExtension(extension);
}

auto NominalTypeDecl::getStoredProperties(bool skipInaccessible) const
Expand Down
Loading