From c8c035ec32b382581aea698e7a5370f1817a5395 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Fri, 9 May 2025 12:10:58 -0500 Subject: [PATCH] [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. --- include/swift/AST/DiagnosticsSema.def | 2 + lib/Sema/MiscDiagnostics.cpp | 38 +++- stdlib/public/core/StringInterpolation.swift | 168 +++++++++++++++--- test/Constraints/diag_ambiguities.swift | 3 +- test/IDE/complete_at_top_level.swift | 8 +- test/IDE/complete_expr_postfix_begin.swift | 14 +- test/IDE/complete_sself.swift | 4 +- test/IDE/complete_value_expr.swift | 14 +- .../diag_unintended_optional_behavior.swift | 40 ++++- test/stdlib/PrintString.swift | 15 ++ 10 files changed, 248 insertions(+), 58 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 4ef66222f15..09a41d670a0 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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, diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index d23f9978059..906da0cd422 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -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(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: )'. 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 walkToExprPre(Expr *E) override { diff --git a/stdlib/public/core/StringInterpolation.swift b/stdlib/public/core/StringInterpolation.swift index e664f521e40..62a2d492aa1 100644 --- a/stdlib/public/core/StringInterpolation.swift +++ b/stdlib/public/core/StringInterpolation.swift @@ -10,11 +10,11 @@ // //===----------------------------------------------------------------------===// -/// Represents a string literal with interpolations while it is 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, +/// Represents a string literal with interpolations while it's being built up. +/// +/// 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( + _ 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( + _ 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( + _ 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( + _ 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 (`\(`...`)`). /// diff --git a/test/Constraints/diag_ambiguities.swift b/test/Constraints/diag_ambiguities.swift index cb328fb0d5e..61ef46a9056 100644 --- a/test/Constraints/diag_ambiguities.swift +++ b/test/Constraints/diag_ambiguities.swift @@ -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 diff --git a/test/IDE/complete_at_top_level.swift b/test/IDE/complete_at_top_level.swift index 8ae4eba3a0a..565931fab74 100644 --- a/test/IDE/complete_at_top_level.swift +++ b/test/IDE/complete_at_top_level.swift @@ -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 diff --git a/test/IDE/complete_expr_postfix_begin.swift b/test/IDE/complete_expr_postfix_begin.swift index d332cd169de..cf24d7bfaf2 100644 --- a/test/IDE/complete_expr_postfix_begin.swift +++ b/test/IDE/complete_expr_postfix_begin.swift @@ -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=.+$}} diff --git a/test/IDE/complete_sself.swift b/test/IDE/complete_sself.swift index ff299b6c1f4..9aa98263903 100644 --- a/test/IDE/complete_sself.swift +++ b/test/IDE/complete_sself.swift @@ -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^# diff --git a/test/IDE/complete_value_expr.swift b/test/IDE/complete_value_expr.swift index 501e7ba3060..430d2186931 100644 --- a/test/IDE/complete_value_expr.swift +++ b/test/IDE/complete_value_expr.swift @@ -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 diff --git a/test/Sema/diag_unintended_optional_behavior.swift b/test/Sema/diag_unintended_optional_behavior.swift index 36a84226064..79c8a71ecec 100644 --- a/test/Sema/diag_unintended_optional_behavior.swift +++ b/test/Sema/diag_unintended_optional_behavior.swift @@ -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(_ 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@-3 {{provide a default value to avoid this warning}} {{55-55= ?? <#default value#>}} + // 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 diff --git a/test/stdlib/PrintString.swift b/test/stdlib/PrintString.swift index 45bfb946c10..58000c93fec 100644 --- a/test/stdlib/PrintString.swift +++ b/test/stdlib/PrintString.swift @@ -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") {