Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[DRAFT] Allow a default for optional interpolations #80547

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beccadax Do you know if it would be possible to only provide this fixit when the string interpolation type is DefaultStringInterpolation?

.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())
Expand Down
40 changes: 40 additions & 0 deletions stdlib/public/core/StringInterpolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,46 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol, Sendable
}
}

extension DefaultStringInterpolation {
@backDeployed(before: SwiftStdlib 6.2)
public mutating func appendInterpolation<T>(
_ 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<T>(
_ 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<T>(
_ value: T?,
default: @autoclosure () -> String
) {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
}

extension DefaultStringInterpolation: CustomStringConvertible {
@inlinable
public var description: String {
Expand Down
12 changes: 8 additions & 4 deletions test/Sema/diag_unintended_optional_behavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions test/stdlib/PrintString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -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") {
Expand All @@ -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") {
Expand Down