[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 *))
NOTE(default_optional_to_any,none,
"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,
"force-unwrap the value to avoid this warning", ())
NOTE(silence_optional_to_any,none,

View File

@@ -5604,7 +5604,7 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E,
segment->getCalledValue(/*skipFunctionConversions=*/true), kind))
if (auto firstArg =
getFirstArgIfUnintendedInterpolation(segment->getArgs(), kind))
diagnoseUnintendedInterpolation(firstArg, kind);
diagnoseUnintendedInterpolation(segment, firstArg, kind);
}
bool interpolationWouldBeUnintended(ConcreteDeclRef appendMethod,
@@ -5670,13 +5670,40 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E,
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
.diagnose(arg->getStartLoc(),
diag::debug_description_in_string_interpolation_segment,
(bool)kind)
.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>)'.
auto argStart = arg->getStartLoc();
Ctx.Diags
@@ -5686,13 +5713,6 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E,
.highlight(arg->getSourceRange())
.fixItInsert(argStart, "String(describing: ")
.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 {

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
/// when you create a string using string interpolation. Instead, use string
/// interpolation to create a new string by including values, literals,
/// You don't need to create an instance of this type directly. It's used by the
/// compiler when you create a string using string interpolation. Instead, use
/// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
@@ -68,8 +68,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Creates a string interpolation with storage pre-sized for a literal
/// with the indicated attributes.
///
/// Do not call this initializer directly. It is used by the compiler when
/// interpreting string interpolations.
/// You don't need to call this initializer directly. It's used by the
/// compiler when interpreting string interpolations.
@inlinable
public init(literalCapacity: Int, interpolationCount: Int) {
let capacityPerInterpolation = 2
@@ -80,8 +80,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Appends a literal segment of a string interpolation.
///
/// Do not call this method directly. It is used by the compiler when
/// interpreting string interpolations.
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations.
@inlinable
public mutating func appendLiteral(_ literal: String) {
literal.write(to: &self)
@@ -90,8 +90,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// Do not call this method directly. It is used by the compiler when
/// interpreting string interpolations. Instead, use string
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
@@ -114,8 +114,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// Do not call this method directly. It is used by the compiler when
/// interpreting string interpolations. Instead, use string
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
@@ -136,8 +136,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// Do not call this method directly. It is used by the compiler when
/// interpreting string interpolations. Instead, use string
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
@@ -160,8 +160,8 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
/// Interpolates the given value's textual representation into the
/// string literal being created.
///
/// Do not call this method directly. It is used by the compiler when
/// interpreting string interpolations. Instead, use string
/// You don't need to call this method directly. It's used by the compiler
/// when interpreting string interpolations. Instead, use string
/// interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// 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 {
@inlinable
public var description: String {
@@ -220,9 +342,9 @@ extension DefaultStringInterpolation: TextOutputStream {
extension String {
/// Creates a new instance from an interpolated string literal.
///
/// Do not call this initializer directly. It is used by the compiler when
/// you create a string using string interpolation. Instead, use string
/// interpolation to create a new string by including values, literals,
/// You don't need to call this initializer directly. It's used by the
/// compiler when you create a string using string interpolation. Instead, use
/// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///
@@ -244,9 +366,9 @@ extension String {
extension Substring {
/// Creates a new instance from an interpolated string literal.
///
/// Do not call this initializer directly. It is used by the compiler when
/// you create a string using string interpolation. Instead, use string
/// interpolation to create a new string by including values, literals,
/// You don't need to call this initializer directly. It's used by the
/// compiler when you create a string using string interpolation. Instead, use
/// string interpolation to create a new string by including values, literals,
/// variables, or expressions enclosed in parentheses, prefixed by a
/// backslash (`\(`...`)`).
///

View File

@@ -38,8 +38,9 @@ func rdar29691909(o: AnyObject) -> Any? {
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?}}
// 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@-3 {{use 'String(describing:)' to silence this warning}}
}
// https://github.com/apple/swift/issues/46300

View File

@@ -295,10 +295,10 @@ func resyncParserB14() {}
var stringInterp = "\(#^STRING_INTERP_3?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[Struct]/CurrModule: FooStruct[#FooStruct#]; name=FooStruct
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Invalid]: fooFunc1()[#Void#];
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule: optStr()[#String?#];
// STRING_INTERP-DAG: Decl[GlobalVar]/Local: fooObject[#FooStruct#];
// STRING_INTERP-DAG: Decl[Struct]/CurrModule/TypeRelation[Convertible]: FooStruct[#FooStruct#]; name=FooStruct
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Convertible]: fooFunc1[#() -> ()#]; name=fooFunc1
// STRING_INTERP-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Convertible]: optStr()[#String?#]; name=optStr()
// STRING_INTERP-DAG: Decl[GlobalVar]/Local/TypeRelation[Convertible]: fooObject[#FooStruct#]; name=fooObject
func resyncParserC1() {}
// FOR_COLLECTION-NOT: forIndex

View File

@@ -27,18 +27,18 @@ protocol FooProtocol {
typealias FooTypealias = Int
// Function parameter
// COMMON-DAG: Decl[LocalVar]/Local: fooParam[#FooStruct#]{{; name=.+$}}
// COMMON-DAG: Decl[LocalVar]/Local{{(/TypeRelation\[Convertible\])?}}: fooParam[#FooStruct#]; name=fooParam
// Global completions
// COMMON-DAG: Decl[Struct]/CurrModule: FooStruct[#FooStruct#]{{; name=.+$}}
// COMMON-DAG: Decl[Enum]/CurrModule: FooEnum[#FooEnum#]{{; name=.+$}}
// COMMON-DAG: Decl[Class]/CurrModule: FooClass[#FooClass#]{{; name=.+$}}
// COMMON-DAG: Decl[Protocol]/CurrModule/Flair[RareType]: FooProtocol[#FooProtocol#]{{; name=.+$}}
// COMMON-DAG: Decl[Struct]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooStruct[#FooStruct#]{{; name=.+$}}
// COMMON-DAG: Decl[Enum]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooEnum[#FooEnum#]{{; name=.+$}}
// COMMON-DAG: Decl[Class]/CurrModule{{(/TypeRelation\[Convertible\])?}}: FooClass[#FooClass#]{{; 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[GlobalVar]/CurrModule: fooObject[#FooStruct#]{{; name=.+$}}
// COMMON-DAG: Decl[GlobalVar]/CurrModule{{(/TypeRelation\[Convertible\])?}}: fooObject[#FooStruct#]{{; 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\])?}}: 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\])?}}: Int16[#Int16#]{{; 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#];
// 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() {
#^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]: instanceVar[#Int#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc0()[#Void#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc1({#(a): Int#})[#Void#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc2({#(a): Int#}, {#b: &Double#})[#Void#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: instanceFunc3({#(a): Int#}, {#(Float, Double)#})[#Void#]{{; name=.+$}}
// 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: instanceFunc5()[#Int?#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal: instanceFunc6()[#Int!#]{{; name=.+$}}
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc0[#() -> ()#]; name=instanceFunc0
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc1(_:)[#(Int) -> ()#]; name=instanceFunc1(_:)
// 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[Convertible]: instanceFunc3(_:_:)[#(Int, (Float, Double)) -> ()#]; name=instanceFunc3(_:_:)
// 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/TypeRelation[Convertible]: instanceFunc5()[#Int?#]; name=instanceFunc5()
// INTERPOLATED_STRING_1-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: instanceFunc6()[#Int!#]; name=instanceFunc6()
//===--- 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?) {
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-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@-4 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}}
var i: Int? = o
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-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@-4 {{use 'String(describing:)' to silence this warning}} {{51-51=String(describing: }} {{52-52=)}}
i = nil
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-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@-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.debugDescription)") // No warning.
@@ -221,8 +235,24 @@ func warnOptionalInStringInterpolationSegment(_ o : Int?) {
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-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@-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

View File

@@ -92,6 +92,11 @@ PrintTests.test("StringInterpolation") {
let s2 = "aaa\(1)bbb\(2 as Any)"
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") {
@@ -100,6 +105,11 @@ PrintTests.test("SubstringInterpolation") {
let s2 = "aaa\(1)bbb\(2 as Any)" as Substring
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") {
@@ -116,6 +126,11 @@ PrintTests.test("AutoCustomStringInterpolation") {
let s2 = ("aaa\(1)bbb\(2 as Any)" as MySimpleString).value
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") {