[6.2][stdlib] Allow a default for optional interpolations (#81360)

Cherry-pick of #80547 for the 6.2 release branch.

---

Explanation: This cherry picks the implementation of SE-0477 to add a
string interpolation method with a `default:` parameter for optional
interpolation values.
Main Branch PR: https://github.com/swiftlang/swift/pull/80547
Risk: Low.
Reviewed By: @stephentyrone 
Resolves: rdar://150865613
Testing: New tests for the string interpolations and fix-its.
This commit is contained in:
Nate Cook
2025-05-09 12:10:58 -05:00
committed by GitHub
parent 51489ac69b
commit c8c035ec32
10 changed files with 248 additions and 58 deletions

View File

@@ -4973,6 +4973,8 @@ NOTE(iuo_to_any_coercion_note_func_result,none,
(const ValueDecl *)) (const ValueDecl *))
NOTE(default_optional_to_any,none, NOTE(default_optional_to_any,none,
"provide a default value to avoid this warning", ()) "provide a default value to avoid this warning", ())
NOTE(default_optional_parameter,none,
"use a default value parameter to avoid this warning", ())
NOTE(force_optional_to_any,none, NOTE(force_optional_to_any,none,
"force-unwrap the value to avoid this warning", ()) "force-unwrap the value to avoid this warning", ())
NOTE(silence_optional_to_any,none, NOTE(silence_optional_to_any,none,

View File

@@ -5604,7 +5604,7 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E,
segment->getCalledValue(/*skipFunctionConversions=*/true), kind)) segment->getCalledValue(/*skipFunctionConversions=*/true), kind))
if (auto firstArg = if (auto firstArg =
getFirstArgIfUnintendedInterpolation(segment->getArgs(), kind)) getFirstArgIfUnintendedInterpolation(segment->getArgs(), kind))
diagnoseUnintendedInterpolation(firstArg, kind); diagnoseUnintendedInterpolation(segment, firstArg, kind);
} }
bool interpolationWouldBeUnintended(ConcreteDeclRef appendMethod, bool interpolationWouldBeUnintended(ConcreteDeclRef appendMethod,
@@ -5670,13 +5670,40 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E,
return firstArg; return firstArg;
} }
void diagnoseUnintendedInterpolation(Expr * arg, UnintendedInterpolationKind kind) { std::string baseInterpolationTypeName(CallExpr *segment) {
if (auto selfApplyExpr = dyn_cast<SelfApplyExpr>(segment->getFn())) {
auto baseType = selfApplyExpr->getBase()->getType();
return baseType->getWithoutSpecifierType()->getString();
}
return "unknown";
}
void diagnoseUnintendedInterpolation(CallExpr *segment,
Expr * arg,
UnintendedInterpolationKind kind) {
Ctx.Diags Ctx.Diags
.diagnose(arg->getStartLoc(), .diagnose(arg->getStartLoc(),
diag::debug_description_in_string_interpolation_segment, diag::debug_description_in_string_interpolation_segment,
(bool)kind) (bool)kind)
.highlight(arg->getSourceRange()); .highlight(arg->getSourceRange());
if (kind == UnintendedInterpolationKind::Optional) {
auto wrappedArgType = arg->getType()->getRValueType()->getOptionalObjectType();
auto baseTypeName = baseInterpolationTypeName(segment);
// Suggest using a default value parameter, but only for non-string values
// when the base interpolation type is the default.
if (!wrappedArgType->isString() && baseTypeName == "DefaultStringInterpolation")
Ctx.Diags.diagnose(arg->getLoc(), diag::default_optional_parameter)
.highlight(arg->getSourceRange())
.fixItInsertAfter(arg->getEndLoc(), ", default: <#default value#>");
// Suggest providing a default value using the nil-coalescing operator.
Ctx.Diags.diagnose(arg->getLoc(), diag::default_optional_to_any)
.highlight(arg->getSourceRange())
.fixItInsertAfter(arg->getEndLoc(), " ?? <#default value#>");
}
// Suggest 'String(describing: <expr>)'. // Suggest 'String(describing: <expr>)'.
auto argStart = arg->getStartLoc(); auto argStart = arg->getStartLoc();
Ctx.Diags Ctx.Diags
@@ -5686,13 +5713,6 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E,
.highlight(arg->getSourceRange()) .highlight(arg->getSourceRange())
.fixItInsert(argStart, "String(describing: ") .fixItInsert(argStart, "String(describing: ")
.fixItInsertAfter(arg->getEndLoc(), ")"); .fixItInsertAfter(arg->getEndLoc(), ")");
if (kind == UnintendedInterpolationKind::Optional) {
// Suggest inserting a default value.
Ctx.Diags.diagnose(arg->getLoc(), diag::default_optional_to_any)
.highlight(arg->getSourceRange())
.fixItInsertAfter(arg->getEndLoc(), " ?? <#default value#>");
}
} }
PreWalkResult<Expr *> walkToExprPre(Expr *E) override { PreWalkResult<Expr *> walkToExprPre(Expr *E) override {

View File

@@ -10,11 +10,11 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
/// Represents a string literal with interpolations while it is being built up. /// Represents a string literal with interpolations while it's being built up.
/// ///
/// Do not create an instance of this type directly. It is used by the compiler /// You don't need to create an instance of this type directly. It's used by the
/// when you create a string using string interpolation. Instead, use string /// compiler when you create a string using string interpolation. Instead, use
/// interpolation to create a new string by including values, literals, /// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
/// ///
@@ -68,8 +68,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Creates a string interpolation with storage pre-sized for a literal /// Creates a string interpolation with storage pre-sized for a literal
/// with the indicated attributes. /// with the indicated attributes.
/// ///
/// Do not call this initializer directly. It is used by the compiler when /// You don't need to call this initializer directly. It's used by the
/// interpreting string interpolations. /// compiler when interpreting string interpolations.
@inlinable @inlinable
public init(literalCapacity: Int, interpolationCount: Int) { public init(literalCapacity: Int, interpolationCount: Int) {
let capacityPerInterpolation = 2 let capacityPerInterpolation = 2
@@ -80,8 +80,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Appends a literal segment of a string interpolation. /// Appends a literal segment of a string interpolation.
/// ///
/// Do not call this method directly. It is used by the compiler when /// You don't need to call this method directly. It's used by the compiler
/// interpreting string interpolations. /// when interpreting string interpolations.
@inlinable @inlinable
public mutating func appendLiteral(_ literal: String) { public mutating func appendLiteral(_ literal: String) {
literal.write(to: &self) literal.write(to: &self)
@@ -90,8 +90,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the /// Interpolates the given value's textual representation into the
/// string literal being created. /// string literal being created.
/// ///
/// Do not call this method directly. It is used by the compiler when /// You don't need to call this method directly. It's used by the compiler
/// interpreting string interpolations. Instead, use string /// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals, /// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
@@ -114,8 +114,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the /// Interpolates the given value's textual representation into the
/// string literal being created. /// string literal being created.
/// ///
/// Do not call this method directly. It is used by the compiler when /// You don't need to call this method directly. It's used by the compiler
/// interpreting string interpolations. Instead, use string /// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals, /// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
@@ -136,8 +136,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the /// Interpolates the given value's textual representation into the
/// string literal being created. /// string literal being created.
/// ///
/// Do not call this method directly. It is used by the compiler when /// You don't need to call this method directly. It's used by the compiler
/// interpreting string interpolations. Instead, use string /// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals, /// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
@@ -160,8 +160,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the /// Interpolates the given value's textual representation into the
/// string literal being created. /// string literal being created.
/// ///
/// Do not call this method directly. It is used by the compiler when /// You don't need to call this method directly. It's used by the compiler
/// interpreting string interpolations. Instead, use string /// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals, /// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
@@ -197,6 +197,128 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
} }
} }
extension DefaultStringInterpolation {
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) where T: TextOutputStreamable, T: CustomStringConvertible {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) where T: TextOutputStreamable {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) where T: CustomStringConvertible {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
/// Interpolates the given optional value's textual representation, or the
/// specified default string, into the string literal being created.
///
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations where you provide a `default`
/// parameter. For example, the following code implicitly calls this method,
/// using the value of the `default` parameter when `value` is `nil`:
///
/// var age: Int? = 48
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is 48
/// age = nil
/// print("Your age is \(age, default: "unknown")")
/// // Prints: Your age is unknown
///
/// - Parameters:
/// - value: The value to include in a string interpolation, if non-`nil`.
/// - default: The string to include if `value` is `nil`.
@_alwaysEmitIntoClient
public mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> some StringProtocol
) {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
}
extension DefaultStringInterpolation: CustomStringConvertible { extension DefaultStringInterpolation: CustomStringConvertible {
@inlinable @inlinable
public var description: String { public var description: String {
@@ -220,9 +342,9 @@ extension DefaultStringInterpolation: TextOutputStream {
extension String { extension String {
/// Creates a new instance from an interpolated string literal. /// Creates a new instance from an interpolated string literal.
/// ///
/// Do not call this initializer directly. It is used by the compiler when /// You don't need to call this initializer directly. It's used by the
/// you create a string using string interpolation. Instead, use string /// compiler when you create a string using string interpolation. Instead, use
/// interpolation to create a new string by including values, literals, /// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
/// ///
@@ -244,9 +366,9 @@ extension String {
extension Substring { extension Substring {
/// Creates a new instance from an interpolated string literal. /// Creates a new instance from an interpolated string literal.
/// ///
/// Do not call this initializer directly. It is used by the compiler when /// You don't need to call this initializer directly. It's used by the
/// you create a string using string interpolation. Instead, use string /// compiler when you create a string using string interpolation. Instead, use
/// interpolation to create a new string by including values, literals, /// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a /// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`). /// backslash (`\(`...`)`).
/// ///

View File

@@ -38,8 +38,9 @@ func rdar29691909(o: AnyObject) -> Any? {
func rdar29907555(_ value: Any!) -> String { func rdar29907555(_ value: Any!) -> String {
return "\(value)" // expected-warning {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}} return "\(value)" // expected-warning {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-1 {{use 'String(describing:)' to silence this warning}} // expected-note@-1 {{use a default value parameter to avoid this warning}}
// expected-note@-2 {{provide a default value to avoid this warning}} // expected-note@-2 {{provide a default value to avoid this warning}}
// expected-note@-3 {{use 'String(describing:)' to silence this warning}}
} }
// https://github.com/apple/swift/issues/46300 // https://github.com/apple/swift/issues/46300

View File

@@ -295,10 +295,10 @@ func resyncParserB14() {}
var stringInterp = "\(#^STRING_INTERP_3?check=STRING_INTERP^#)" var stringInterp = "\(#^STRING_INTERP_3?check=STRING_INTERP^#)"
_ = "" + "\(#^STRING_INTERP_4?check=STRING_INTERP^#)" + "" _ = "" + "\(#^STRING_INTERP_4?check=STRING_INTERP^#)" + ""
// STRING_INTERP-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]/IsSystem: ['(']{#(value): any Any.Type#}[')'][#Void#]; // STRING_INTERP-DAG: Decl[InstanceMethod]/CurrNominal/Flair[ArgLabels]/IsSystem: ['(']{#(value): any Any.Type#}[')'][#Void#];
// STRING_INTERP-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]; name=FooStruct // STRING_INTERP-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: FooStruct[#FooStruct#]; name=FooStruct
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Invalid]: fooFunc1()[#Void#]; // STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Convertible]: fooFunc1[#() -> ()#]; name=fooFunc1
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule: optStr()[#String?#]; // STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Convertible]: optStr()[#String?#]; name=optStr()
// STRING_INTERP-DAG: Decl[GlobalVar]/Local: fooObject[#FooStruct#]; // STRING_INTERP-DAG: Decl[GlobalVar]/Local/TypeRelation[Convertible]: fooObject[#FooStruct#]; name=fooObject
func resyncParserC1() {} func resyncParserC1() {}
// FOR_COLLECTION-NOT: forIndex // FOR_COLLECTION-NOT: forIndex

View File

@@ -27,18 +27,18 @@ protocol FooProtocol {
typealias FooTypealias = Int typealias FooTypealias = Int
// Function parameter // Function parameter
// COMMON-DAG: Decl[LocalVar]/Local: fooParam[#FooStruct#]{{; name=.+$}} // COMMON-DAG: Decl[LocalVar]/Local{{(/TypeRelation\[Convertible\])?}}: fooParam[#FooStruct#]; name=fooParam
// Global completions // Global completions
// COMMON-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}} // COMMON-DAG: Decl[Struct]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooStruct[#FooStruct#]{{; name=.+$}}
// COMMON-DAG: Decl[Enum]/CurrModule: FooEnum[#FooEnum#]{{; name=.+$}} // COMMON-DAG: Decl[Enum]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooEnum[#FooEnum#]{{; name=.+$}}
// COMMON-DAG: Decl[Class]/CurrModule: FooClass[#FooClass#]{{; name=.+$}} // COMMON-DAG: Decl[Class]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooClass[#FooClass#]{{; name=.+$}}
// COMMON-DAG: Decl[Protocol]/CurrModule/Flair[RareType]: FooProtocol[#FooProtocol#]{{; name=.+$}} // COMMON-DAG: Decl[Protocol]/CurrModule/Flair[RareType]{{(/TypeRelation\[Convertible\])?}}: FooProtocol[#FooProtocol#]{{; name=.+$}}
// COMMON-DAG: Decl[TypeAlias]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooTypealias[#Int#]{{; name=.+$}} // COMMON-DAG: Decl[TypeAlias]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooTypealias[#Int#]{{; name=.+$}}
// COMMON-DAG: Decl[GlobalVar]/CurrModule: fooObject[#FooStruct#]{{; name=.+$}} // COMMON-DAG: Decl[GlobalVar]/CurrModule{{(/TypeRelation\[Convertible\])?}}: fooObject[#FooStruct#]{{; name=.+$}}
// COMMON-DAG: Keyword[try]/None: try{{; name=.+$}} // COMMON-DAG: Keyword[try]/None: try{{; name=.+$}}
// COMMON-DAG: Literal[Boolean]/None{{(/TypeRelation\[Convertible\])?}}: true[#Bool#]{{; name=.+$}} // COMMON-DAG: Literal[Boolean]/None{{(/TypeRelation\[Convertible\])?}}: true[#Bool#]{{; name=.+$}}
// COMMON-DAG: Literal[Boolean]/None{{(/TypeRelation\[Convertible\])?}}: false[#Bool#]{{; name=.+$}} // COMMON-DAG: Literal[Boolean]/None{{(/TypeRelation\[Convertible\])?}}: false[#Bool#]{{; name=.+$}}
// COMMON-DAG: Literal[Nil]/None: nil{{; name=.+$}} // COMMON-DAG: Literal[Nil]/None{{(/TypeRelation\[Convertible\])?}}: nil{{.*; name=.+$}}
// COMMON-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem{{(/TypeRelation\[Convertible\])?}}: Int8[#Int8#]{{; name=.+$}} // COMMON-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem{{(/TypeRelation\[Convertible\])?}}: Int8[#Int8#]{{; name=.+$}}
// COMMON-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem{{(/TypeRelation\[Convertible\])?}}: Int16[#Int16#]{{; name=.+$}} // COMMON-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem{{(/TypeRelation\[Convertible\])?}}: Int16[#Int16#]{{; name=.+$}}
// COMMON-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem{{(/TypeRelation\[Convertible\])?}}: Int32[#Int32#]{{; name=.+$}} // COMMON-DAG: Decl[Struct]/OtherModule[Swift]/IsSystem{{(/TypeRelation\[Convertible\])?}}: Int32[#Int32#]{{; name=.+$}}

View File

@@ -4,9 +4,9 @@
// GENERICPARAM: Decl[GenericTypeParam]/Local: Self[#Self#]; // GENERICPARAM: Decl[GenericTypeParam]/Local: Self[#Self#];
// STATICSELF: Keyword[Self]/CurrNominal: Self[#S#]; // STATICSELF: Keyword[Self]/CurrNominal{{(/TypeRelation\[Convertible\])?}}: Self[#S#];
// DYNAMICSELF: Keyword[Self]/CurrNominal: Self[#Self#]; // DYNAMICSELF: Keyword[Self]/CurrNominal{{(/TypeRelation\[Convertible\])?}}: Self[#Self#];
func freeFunc() { func freeFunc() {
#^GLOBAL_BODY_EXPR?check=NOSELF^# #^GLOBAL_BODY_EXPR?check=NOSELF^#

View File

@@ -1123,13 +1123,13 @@ func testInterpolatedString1() {
// INTERPOLATED_STRING_1-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: lazyInstanceVar[#Int#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: lazyInstanceVar[#Int#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: instanceVar[#Int#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceVar]/CurrNominal/TypeRelation[Convertible]: instanceVar[#Int#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc0()[#Void#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc0[#() -> ()#]; name=instanceFunc0
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc1({#(a): Int#})[#Void#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc1(_:)[#(Int) -> ()#]; name=instanceFunc1(_:)
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc2({#(a): Int#}, {#b: &Double#})[#Void#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc2(_:b:)[#(Int, inout Double) -> ()#]; name=instanceFunc2(_:b:)
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc3({#(a): Int#}, {#(Float, Double)#})[#Void#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc3(_:_:)[#(Int, (Float, Double)) -> ()#]; name=instanceFunc3(_:_:)
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc4({#(a): Int?#}, {#b: Int!#}, {#c: &Int?#}, {#d: &Int!#})[#Void#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc4(_:b:c:d:)[#(Int?, Int?, inout Int?, inout Int?) -> ()#]; name=instanceFunc4(_:b:c:d:)
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal: instanceFunc5()[#Int?#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc5()[#Int?#]; name=instanceFunc5()
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal: instanceFunc6()[#Int!#]{{; name=.+$}} // INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc6()[#Int!#]; name=instanceFunc6()
//===--- Check protocol extensions //===--- Check protocol extensions

View File

@@ -196,21 +196,35 @@ extension DefaultStringInterpolation {
} }
} }
/// A type that provides a custom `StringInterpolation` type.
struct CustomInterpolation: ExpressibleByStringInterpolation {
struct StringInterpolation: StringInterpolationProtocol {
init(literalCapacity: Int, interpolationCount: Int) {}
mutating func appendLiteral(_ literal: String) {}
mutating func appendInterpolation<T>(_ interp: T) {}
}
init(stringInterpolation: StringInterpolation) {}
init(stringLiteral: String) {}
}
func warnOptionalInStringInterpolationSegment(_ o : Int?) { func warnOptionalInStringInterpolationSegment(_ o : Int?) {
print("Always some, Always some, Always some: \(o)") print("Always some, Always some, Always some: \(o)")
// expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}} // expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-2 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}} // expected-note@-2 {{use a default value parameter to avoid this warning}} {{52-52=, default: <#default value#>}}
// expected-note@-3 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}} // expected-note@-3 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}}
// expected-note@-4 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}}
var i: Int? = o var i: Int? = o
print("Always some, Always some, Always some: \(i)") print("Always some, Always some, Always some: \(i)")
// expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}} // expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-2 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}} // expected-note@-2 {{use a default value parameter to avoid this warning}} {{52-52=, default: <#default value#>}}
// expected-note@-3 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}} // expected-note@-3 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}}
// expected-note@-4 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}}
i = nil i = nil
print("Always some, Always some, Always some: \(o.map { $0 + 1 })") print("Always some, Always some, Always some: \(o.map { $0 + 1 })")
// expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}} // expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-2 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{67-67=)}} // expected-note@-2 {{use a default value parameter to avoid this warning}} {{67-67=, default: <#default value#>}}
// expected-note@-3 {{provide a default value to avoid this warning}} {{67-67= ?? <#default value#>}} // expected-note@-3 {{provide a default value to avoid this warning}} {{67-67= ?? <#default value#>}}
// expected-note@-4 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{67-67=)}}
print("Always some, Always some, Always some: \(o as Int?)") // No warning print("Always some, Always some, Always some: \(o as Int?)") // No warning
print("Always some, Always some, Always some: \(o.debugDescription)") // No warning. print("Always some, Always some, Always some: \(o.debugDescription)") // No warning.
@@ -221,8 +235,24 @@ func warnOptionalInStringInterpolationSegment(_ o : Int?) {
print("Always some, Always some, Always some: \(ooST)") print("Always some, Always some, Always some: \(ooST)")
// expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}} // expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-2 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{55-55=)}} // expected-note@-2 {{use a default value parameter to avoid this warning}} {{55-55=, default: <#default value#>}}
// expected-note@-3 {{provide a default value to avoid this warning}} {{55-55= ?? <#default value#>}} // expected-note@-3 {{provide a default value to avoid this warning}} {{55-55= ?? <#default value#>}}
// expected-note@-4 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{55-55=)}}
// Don't provide a `\(_:default:)` fix-it for optional strings; the `??`
// operator is the recommended warning resolution.
let s: String? = o.map(String.init)
print("Always some, Always some, Always some: \(s)")
// expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-2 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}}
// expected-note@-3 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}}
// Don't provide a `\(_:default:)` fix-it for interpolations that don't resolve
// to strings.
let _: CustomInterpolation = "Always some, Always some, Always some: \(o)"
// expected-warning@-1 {{string interpolation produces a debug description for an optional value; did you mean to make this explicit?}}
// expected-note@-2 {{provide a default value to avoid this warning}} {{75-75= ?? <#default value#>}}
// expected-note@-3 {{use 'String(describing:)' to silence this warning}} {{74-74=String(describing: }} {{75-75=)}}
} }
// Make sure we don't double diagnose // Make sure we don't double diagnose

View File

@@ -92,6 +92,11 @@ PrintTests.test("StringInterpolation") {
let s2 = "aaa\(1)bbb\(2 as Any)" let s2 = "aaa\(1)bbb\(2 as Any)"
expectEqual("aaa1bbb2", s2) expectEqual("aaa1bbb2", s2)
let x: Int? = 1
let y: Int? = nil
let s3 = "aaa\(x, default: "NONE")bbb\(y, default: "NONE")"
expectEqual("aaa1bbbNONE", s3)
} }
PrintTests.test("SubstringInterpolation") { PrintTests.test("SubstringInterpolation") {
@@ -100,6 +105,11 @@ PrintTests.test("SubstringInterpolation") {
let s2 = "aaa\(1)bbb\(2 as Any)" as Substring let s2 = "aaa\(1)bbb\(2 as Any)" as Substring
expectEqual("aaa1bbb2", s2) expectEqual("aaa1bbb2", s2)
let x: Int? = 1
let y: Int? = nil
let s3 = "aaa\(x, default: "NONE")bbb\(y, default: "NONE")" as Substring
expectEqual("aaa1bbbNONE", s3)
} }
PrintTests.test("CustomStringInterpolation") { PrintTests.test("CustomStringInterpolation") {
@@ -116,6 +126,11 @@ PrintTests.test("AutoCustomStringInterpolation") {
let s2 = ("aaa\(1)bbb\(2 as Any)" as MySimpleString).value let s2 = ("aaa\(1)bbb\(2 as Any)" as MySimpleString).value
expectEqual("aaa1bbb2", s2) expectEqual("aaa1bbb2", s2)
let x: Int? = 1
let y: Int? = nil
let s3 = ("aaa\(x, default: "NONE")bbb\(y, default: "NONE")" as MySimpleString).value
expectEqual("aaa1bbbNONE", s3)
} }
PrintTests.test("CustomStringInterpolationExtra") { PrintTests.test("CustomStringInterpolationExtra") {