diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 6a3922506c63c..a1fceccd8f9b3 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4958,6 +4958,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, + "provide a default value parameter", ()) 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 cf7a097021ca7..a3c8c2ebca899 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -5688,6 +5688,11 @@ static void diagnoseUnintendedOptionalBehavior(const Expr *E, .fixItInsertAfter(arg->getEndLoc(), ")"); if (kind == UnintendedInterpolationKind::Optional) { + // Suggest using a default interpolation value parameter. + Ctx.Diags.diagnose(arg->getLoc(), diag::default_optional_parameter) + .highlight(arg->getSourceRange()) + .fixItInsertAfter(arg->getEndLoc(), ", default: <#default value#>"); + // Suggest inserting a default value. Ctx.Diags.diagnose(arg->getLoc(), diag::default_optional_to_any) .highlight(arg->getSourceRange()) diff --git a/stdlib/public/core/StringInterpolation.swift b/stdlib/public/core/StringInterpolation.swift index e664f521e4091..ad75d37c8f13f 100644 --- a/stdlib/public/core/StringInterpolation.swift +++ b/stdlib/public/core/StringInterpolation.swift @@ -197,6 +197,46 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable } } +extension DefaultStringInterpolation { + @backDeployed(before: SwiftStdlib 6.2) + public mutating func appendInterpolation( + _ value: T?, + default: @autoclosure () -> String + ) where T: TextOutputStreamable { + if let value { + self.appendInterpolation(value) + } else { + self.appendInterpolation(`default`()) + } + } + + @backDeployed(before: SwiftStdlib 6.2) + @_disfavoredOverload + public mutating func appendInterpolation( + _ value: T?, + default: @autoclosure () -> String + ) where T: CustomStringConvertible { + if let value { + self.appendInterpolation(value) + } else { + self.appendInterpolation(`default`()) + } + } + + @backDeployed(before: SwiftStdlib 6.2) + @_disfavoredOverload + public mutating func appendInterpolation( + _ value: T?, + default: @autoclosure () -> String + ) { + if let value { + self.appendInterpolation(value) + } else { + self.appendInterpolation(`default`()) + } + } +} + extension DefaultStringInterpolation: CustomStringConvertible { @inlinable public var description: String { diff --git a/test/Sema/diag_unintended_optional_behavior.swift b/test/Sema/diag_unintended_optional_behavior.swift index 36a84226064fd..772e2ac3d0bd1 100644 --- a/test/Sema/diag_unintended_optional_behavior.swift +++ b/test/Sema/diag_unintended_optional_behavior.swift @@ -200,17 +200,20 @@ 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@-3 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}} + // expected-note@-3 {{provide a default value parameter}} {{52-52=, default: <#default value#>}} + // expected-note@-4 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}} 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@-3 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}} + // expected-note@-3 {{provide a default value parameter}} {{52-52=, default: <#default value#>}} + // expected-note@-4 {{provide a default value to avoid this warning}} {{52-52= ?? <#default value#>}} 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@-3 {{provide a default value to avoid this warning}} {{67-67= ?? <#default value#>}} + // expected-note@-3 {{provide a default value parameter}} {{67-67=, default: <#default value#>}} + // expected-note@-4 {{provide a default value to avoid this warning}} {{67-67= ?? <#default value#>}} print("Always some, Always some, Always some: \(o as Int?)") // No warning print("Always some, Always some, Always some: \(o.debugDescription)") // No warning. @@ -222,7 +225,8 @@ 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@-3 {{provide a default value parameter}} {{55-55=, default: <#default value#>}} + // expected-note@-4 {{provide a default value to avoid this warning}} {{55-55= ?? <#default value#>}} } // Make sure we don't double diagnose diff --git a/test/stdlib/PrintString.swift b/test/stdlib/PrintString.swift index 45bfb946c10fd..58000c93fecd1 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") {