Files
swift-mirror/test/Constraints/if_expr.swift
Pavel Yaskevich b12d844fcc [CSSimplify] Remove ad-hoc optional mismatch handling for arguments
Since we have `repairViaOptionalUnwrap`, let's use that to fix
argument/parameter mismatches instead of doing ad-hoc matching.
2025-05-30 09:52:57 -07:00

732 lines
15 KiB
Swift

// RUN: %target-typecheck-verify-swift
enum E {
case e
case f
case g(Int)
}
func testDotSyntax1() -> E {
if .random() { .e } else { .f }
}
func testDotSyntax2() -> E? {
if .random() { .e } else { .f }
}
func testDotSyntax3() -> E? {
if .random() { .e } else { .none }
}
func testDotSyntax4() -> Int {
let i = if .random() { 0 } else { .random() }
// expected-error@-1 {{cannot infer contextual base in reference to member 'random'}}
return i
}
let testVar1: E = if .random() { .e } else { .f }
let testVar2: E? = if .random() { .e } else { .f }
let testVar3: E? = if .random() { .e } else { .none }
let testVar4: E? = if .random() { nil } else { .e }
let testVar5 = if .random() { 0 } else { 1.0 }
// expected-error@-1 {{branches have mismatching types 'Int' and 'Double'}}
let testVar6: Double = if .random() { 0 } else { 1.0 }
let testVar7: Double = if .random() { 0 + 1 } else if .random() { 1.0 + 3 } else { 9 + 0.0 }
let testContextualMismatch1: String = if .random() { 1 } else { "" }
// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}}
let testContextualMismatch2: String = if .random() { 1 } else { 2 }
// expected-error@-1 {{cannot convert value of type 'Int' to specified type 'String'}}
let testMismatch1 = if .random() { 1 } else if .random() { "" } else { 0.0 }
// expected-error@-1 {{branches have mismatching types 'Int' and 'Double'}}
// expected-error@-2 {{branches have mismatching types 'String' and 'Double'}}
func testMismatch2() -> Double {
let x = if .random() {
0 // expected-error {{branches have mismatching types 'Int' and 'Double'}}
} else if .random() {
0
} else {
1.0
}
return x
}
func testOptionalBinding1(_ x: Int?) -> Int {
if let x = x { x } else { 0 }
}
func testOptionalBinding2(_ x: Int?, _ y: Int?) -> Int {
if let x = x, let y = y { x + y } else { 0 }
}
func testPatternBinding2(_ e: E) -> Int {
if case .g(let i) = e { i } else { 0 }
}
func testTernary() -> Int {
if .random() { .random() ? 1 : 0 } else { .random() ? 3 : 2 }
}
func testReturn() -> Int {
if .random() {
return 0
} else {
1 // expected-warning {{integer literal is unused}}
}
}
func testNil1(_ x: Bool) {
let _ = if x { 42 } else { nil } // expected-error {{'nil' requires a contextual type}}
}
func testNil2(_ x: Bool) {
let _ = if x { nil } else { 42 } // expected-error {{'nil' requires a contextual type}}
}
func testNil3(_ x: Bool) {
// In this case, we allow propagating the type from the first branch into
// later branches.
let _: _? = if x { 42 } else { nil }
}
func testNil4(_ x: Bool) {
let _: _? = if x { nil } else { 42 } // expected-error {{could not infer type for placeholder}}
}
enum F<T> {
// expected-note@-1 {{arguments to generic parameter 'T' ('Double' and 'Int') are expected to be equal}}
// expected-note@-2 {{'T' declared as parameter to type 'F'}}
case x(T), y
}
func testUnboundGeneric1() -> F<Int> {
let x: F = if .random() { .x(5) } else { .x(1) }
return x
}
func testUnboundGeneric2() -> F<Int> {
let x: F = if .random() { .x(5) } else { .y }
return x
}
func testUnboundGeneric3() -> F<Int> {
let x: F = if .random() { .y } else { .y }
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
return x
}
func testUnboundGeneric4() -> F<Int> {
let x: F = if .random() { .x(5.0) } else { .x(5.0) }
return x
// expected-error@-1 {{cannot convert return expression of type 'F<Double>' to return type 'F<Int>'}}
}
func testUnboundGeneric5() -> F<Int> {
let x: F = if .random() { .x(5) } else { .x(5.0) }
// expected-error@-1 {{cannot convert value of type 'Double' to expected argument type 'Int'}}
return x
}
protocol Q {
associatedtype X
}
struct SQ : Q {
typealias X = String
}
func testAssociatedTypeReturn1() {
func fn<T : Q>(_ fn: (T) -> T.X) {}
fn { x in // expected-error {{cannot infer type of closure parameter 'x' without a type annotation}}
if .random() { "" } else { "" }
}
fn { (x: SQ) in
if .random() { "" } else { "" }
}
fn { (x: SQ) in
if .random() { "" } else { 0 } // expected-error {{cannot convert value of type 'Int' to specified type 'SQ.X' (aka 'String')}}
}
}
func testNeverConversion1() -> Int {
if .random() {
1
} else {
fatalError()
}
}
func testNeverConversion2() -> Int {
return if .random() {
1
} else {
fatalError()
}
}
func testNeverConversion3() -> Int {
if .random() {
1
} else {
if .random() {
fatalError()
} else {
2
}
}
}
func testNeverConversion4() -> Int {
return if .random() {
1
} else {
if .random() {
fatalError()
} else {
2
}
}
}
func testNeverConversion5() -> Int {
{
if .random() {
1
} else {
if .random() {
fatalError()
} else {
2
}
}
}()
}
func testVoidConversion() {
func foo(_ fn: () -> Void) {}
func bar<T>(_ fn: () -> T) {}
// Okay for an implicit return, including nested as this preserves source
// compatibility.
foo {
if .random() {
0 // expected-warning {{integer literal is unused}}
} else {
0 // expected-warning {{integer literal is unused}}
}
}
foo {
if .random() {
0 // expected-warning {{integer literal is unused}}
} else {
if .random() {
0 // expected-warning {{integer literal is unused}}
} else {
0 // expected-warning {{integer literal is unused}}
}
}
}
bar {
if .random() {
0
} else {
0
}
}
bar {
if .random() {
0
} else {
if .random() {
0
} else {
0
}
}
}
bar {
if .random() {
()
} else {
0 // expected-warning {{integer literal is unused}}
}
}
bar {
if .random() {
0 // expected-warning {{integer literal is unused}}
} else {
if .random() {
()
} else {
0 // expected-warning {{integer literal is unused}}
}
}
}
// We allow the branches to mismatch to preserve source compatibility.
// (this example is silly, but this occurs in the wild for more innocuous
// things like branches that do set insertions and removals).
bar {
if .random() { 0 } else { "" }
// expected-warning@-1 {{integer literal is unused}}
// expected-warning@-2 {{string literal is unused}}
}
bar {
if .random() {
if .random() {
0 // expected-warning {{integer literal is unused}}
} else {
[0] // expected-warning {{expression of type '[Int]' is unused}}
}
} else {
"" // expected-warning {{string literal is unused}}
}
}
bar { () -> Void in
if .random() { 0 } else { "" }
// expected-warning@-1 {{integer literal is unused}}
// expected-warning@-2 {{string literal is unused}}
}
bar { () -> Void in
if .random() {
if .random() {
0 // expected-warning {{integer literal is unused}}
} else {
[0] // expected-warning {{expression of type '[Int]' is unused}}
}
} else {
"" // expected-warning {{string literal is unused}}
}
}
bar { () -> Int in
if .random() { 0 } else { "" } // expected-error {{cannot convert value of type 'String' to specified type 'Int'}}
}
bar { () -> Int in
if .random() {
if .random() {
0
} else {
[0] // expected-error {{cannot convert value of type '[Int]' to specified type 'Int'}}
}
} else {
""
}
}
// Not okay for an explicit return.
foo {
return if .random() {
0 // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}}
} else {
0
}
}
foo {
return if .random() {
0 // expected-error {{cannot convert value of type 'Int' to specified type 'Void'}}
} else {
if .random() {
0
} else {
0
}
}
}
bar {
return if .random() {
0
} else {
0
}
}
bar {
return if .random() {
0
} else {
if .random() {
0
} else {
0
}
}
}
bar {
return if .random() {
() // expected-error {{branches have mismatching types '()' and 'Int'}}
} else {
0
}
}
bar {
return if .random() {
0
} else {
if .random() {
() // expected-error {{branches have mismatching types '()' and 'Int'}}
} else {
0
}
}
}
bar {
return if .random() { 0 } else { "" } // expected-error {{branches have mismatching types 'Int' and 'String'}}
}
bar {
return if .random() {
if .random() {
0 // expected-error {{branches have mismatching types 'Int' and '[Int]'}}
} else {
[0]
}
} else {
""
}
}
}
func testReturnMismatch() {
let _ = if .random() {
return 1
// expected-error@-1 {{cannot use 'return' to transfer control out of 'if' expression}}
// expected-error@-2 {{unexpected non-void return value in void function}}
// expected-note@-3 {{did you mean to add a return type?}}
} else {
0
}
}
func testOptionalGeneric() {
func bar<T>(_ fn: () -> T?) -> T? { fn() }
bar {
if .random() {
()
} else {
()
}
}
}
func testNestedOptional() -> Int? {
if .random() {
1
} else {
if .random() {
1
} else {
nil
}
}
}
let neverVar = if .random() { fatalError() } else { fatalError() }
// expected-warning@-1 {{constant 'neverVar' inferred to have type 'Never'}}
// expected-note@-2 {{add an explicit type annotation to silence this warning}}
func testNonVoidToVoid() {
if .random() { 0 } else { 1 } // expected-warning 2{{integer literal is unused}}
}
func uninferableNil() {
let _ = if .random() { nil } else { 2.0 } // expected-error {{'nil' requires a contextual type}}
}
func testAssignment() {
var d: Double = if .random() { 0 } else { 1.0 }
d = if .random() { 0 } else { 1 }
_ = d
}
struct TestBadReturn {
var y = if .random() { return } else { 0 }
// expected-error@-1 {{return invalid outside of a func}}
// expected-error@-2 {{cannot use 'return' to transfer control out of 'if' expression}}
}
struct SomeError: Error {}
func testThrowInference() {
func notThrowing(_ fn: () -> Int) {}
notThrowing { // expected-error {{invalid conversion from throwing function of type '() throws -> Int' to non-throwing function type '() -> Int'}}
if .random() {
0
} else {
throw SomeError()
}
}
@discardableResult
func rethrowing<T>(_ fn: () throws -> T) rethrows -> T { try fn() }
rethrowing {
// expected-error@-1 {{call can throw, but it is not marked with 'try' and the error is not handled}}
// expected-note@-2 {{call is to 'rethrows' function, but argument function can throw}}
if .random() {
0
} else {
throw SomeError()
}
}
}
// MARK: Pound if
func testPoundIf1() -> Int {
if .random() {
#if true
0
#else
""
#endif
} else {
0
}
}
func testPoundIf2() -> String {
if .random() {
#if true
0 // expected-error {{cannot convert value of type 'Int' to specified type 'String'}}
#else
""
#endif
} else {
""
}
}
func testPoundIf3() -> String {
if .random() {
#if false
0
#else
""
#endif
} else {
""
}
}
func testPoundIf4() -> String {
let x = if .random() {
#if true
0 // expected-error {{branches have mismatching types 'Int' and 'String'}}
#else
""
#endif
} else {
""
}
return x
}
func testPoundIf5() -> String {
let x = if .random() {
#if false
0
#else
""
#endif
} else {
""
}
return x
}
func testPoundIfClosure1() -> Int {
let fn = {
if .random() {
#if true
0
#else
""
#endif
} else {
0
}
}
return fn()
}
func testPoundIfClosure2() -> String {
let fn: () -> String = {
if .random() {
#if true
0 // expected-error {{cannot convert value of type 'Int' to specified type 'String'}}
#else
""
#endif
} else {
""
}
}
return fn()
}
// MARK: Subtyping
class A {}
class B : A {}
class C : A {}
func testSubtyping1() -> A {
// We can join to A.
let x = if .random() { B() } else { C() }
let y = .random() ? B() : C()
if .random() {
return x
} else {
return y
}
}
// MARK: Opaque result types
protocol P {}
extension Int : P {}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
func testOpaqueReturn1() -> some P {
if .random() {
0
} else {
1
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
func testOpaqueReturn2() -> some P {
if .random() {
0
} else {
fatalError()
}
}
// MARK: Result builders
enum Either<T, U> {
case first(T), second(U)
}
@resultBuilder
struct Builder {
static func buildBlock<T>(_ x: T) -> T { x }
static func buildBlock<T, U>(_ x: T, _ y: U) -> (T, U) { (x, y) }
static func buildEither<T, U>(first x: T) -> Either<T, U> { .first(x) }
static func buildEither<T, U>(second x: U) -> Either<T, U> { .second(x) }
static func buildExpression(_ x: Double) -> Double { x }
static func buildExpression<T>(_ x: T) -> T { x }
}
@Builder
func singleExprBuilder() -> Either<String, Int> {
if .random() {
""
} else {
1
}
}
@Builder
func builderStaticMember() -> (Either<String, Int>, Double) {
if .random() {
""
} else {
1
}
.pi // This becomes a static member ref, not a member on an if expression.
}
@Builder
func builderNotPostfix() -> (Either<String, Int>, Bool) {
if .random() { "" } else { 1 } !.random() // expected-error {{consecutive statements on a line must be separated by ';'}}
}
@Builder
func builderWithBinding() -> Either<String, Int> {
// Make sure the binding gets type-checked as an if expression, but the
// other if block gets type-checked as a stmt.
let str = if .random() { "a" } else { "b" }
if .random() {
str
} else {
1
}
}
@Builder
func builderWithInvalidBinding() -> Either<String, Int> {
let str = (if .random() { "a" } else { "b" })
// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
if .random() {
str
} else {
1
}
}
func takesBuilder(@Builder _ fn: () -> Either<String, Int>) {}
func builderClosureWithBinding() {
takesBuilder {
// Make sure the binding gets type-checked as an if expression, but the
// other if block gets type-checked as a stmt.
let str = if .random() { "a" } else { "b" }
if .random() {
str
} else {
1
}
}
}
func builderClosureWithInvalidBinding() {
takesBuilder {
let str = (if .random() { "a" } else { "b" })
// expected-error@-1 {{'if' may only be used as expression in return, throw, or as the source of an assignment}}
if .random() {
str
} else {
1
}
}
}
func builderInClosure() {
takesBuilder {
if .random() {
""
} else {
1
}
}
}
// https://github.com/apple/swift/issues/63796
func testInvalidOptionalChainingInIfContext() {
let v63796 = 1
if v63796? {} // expected-error{{cannot use optional chaining on non-optional value of type 'Int'}}
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
}
// https://github.com/swiftlang/swift/issues/79395
_ = {
if .random() {
struct S: Error {}
throw S()
} else {
1
}
}
_ = {
if .random() {
if .random() {
struct S: Error {}
throw S()
} else {
0
}
} else {
1
}
}