Allow Comparable: ~Copyable (#85747)

Builds on #85746 which covers Equatable
This commit is contained in:
Ben Cohen
2025-12-05 08:37:36 -08:00
committed by GitHub
parent a7f996651c
commit 0f99458900
7 changed files with 148 additions and 24 deletions

View File

@@ -135,7 +135,7 @@
/// (`FloatingPoint.nan`) compares as neither less than, greater than, nor
/// equal to any normal floating-point value. Exceptional values need not
/// take part in the strict total order.
public protocol Comparable: Equatable {
public protocol Comparable: Equatable, ~Copyable {
/// Returns a Boolean value indicating whether the value of the first
/// argument is less than that of the second argument.
///
@@ -146,7 +146,7 @@ public protocol Comparable: Equatable {
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
static func < (lhs: Self, rhs: Self) -> Bool
static func < (lhs: borrowing Self, rhs: borrowing Self) -> Bool
/// Returns a Boolean value indicating whether the value of the first
/// argument is less than or equal to that of the second argument.
@@ -154,7 +154,7 @@ public protocol Comparable: Equatable {
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
static func <= (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: borrowing Self, rhs: borrowing Self) -> Bool
/// Returns a Boolean value indicating whether the value of the first
/// argument is greater than or equal to that of the second argument.
@@ -162,7 +162,7 @@ public protocol Comparable: Equatable {
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
static func >= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: borrowing Self, rhs: borrowing Self) -> Bool
/// Returns a Boolean value indicating whether the value of the first
/// argument is greater than that of the second argument.
@@ -170,10 +170,10 @@ public protocol Comparable: Equatable {
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
static func > (lhs: Self, rhs: Self) -> Bool
static func > (lhs: borrowing Self, rhs: borrowing Self) -> Bool
}
extension Comparable {
extension Comparable where Self: ~Copyable {
/// Returns a Boolean value indicating whether the value of the first argument
/// is greater than that of the second argument.
///
@@ -184,7 +184,8 @@ extension Comparable {
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable
public static func > (lhs: Self, rhs: Self) -> Bool {
@_preInverseGenerics
public static func > (lhs: borrowing Self, rhs: borrowing Self) -> Bool {
return rhs < lhs
}
@@ -198,7 +199,8 @@ extension Comparable {
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable
public static func <= (lhs: Self, rhs: Self) -> Bool {
@_preInverseGenerics
public static func <= (lhs: borrowing Self, rhs: borrowing Self) -> Bool {
return !(rhs < lhs)
}
@@ -214,7 +216,8 @@ extension Comparable {
/// - Returns: `true` if `lhs` is greater than or equal to `rhs`; otherwise,
/// `false`.
@inlinable
public static func >= (lhs: Self, rhs: Self) -> Bool {
@_preInverseGenerics
public static func >= (lhs: borrowing Self, rhs: borrowing Self) -> Bool {
return !(lhs < rhs)
}
}

View File

@@ -1472,14 +1472,12 @@ func testUnwrapFixIts(x: Int?) throws {
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{11-11=!}}
foo(y: x ?? 0)
let _ = x < 2 // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{12-12= ?? <#default value#>}}
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{12-12=!}}
let _ = x < 2 // expected-error {{binary operator '<' cannot be applied to operands of type 'Int?' and 'Int'}}
// expected-note@-1 {{overloads for '<' exist with these partially matching parameter lists: (Int, Int)}}
let _ = x ?? 0 < 2
let _ = 2 < x // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{16-16= ?? <#default value#>}}
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{16-16=!}}
let _ = 2 < x // expected-error {{binary operator '<' cannot be applied to operands of type 'Int' and 'Int?'}}
// expected-note@-1 {{overloads for '<' exist with these partially matching parameter lists: (Int, Int)}}
let _ = 2 < x ?? 0
let _: Int = (.optionalIntMember) // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
@@ -1497,9 +1495,8 @@ func testUnwrapFixIts(x: Int?) throws {
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{36-36=!}}
let _ = try (.optionalThrowsMember ?? 0) + 1
let _ = .optionalIntMember?.bitWidth > 0 // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{39-39= ?? <#default value#>}}
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{11-11=(}} {{39-39=)!}}
let _ = .optionalIntMember?.bitWidth > 0 // expected-error {{binary operator '>' cannot be applied to operands of type 'Int?' and 'Int'}}
// expected-note@-1 {{overloads for '>' exist with these partially matching parameter lists: (Int, Int)}}
let _ = (.optionalIntMember?.bitWidth)! > 0
let _ = .optionalIntMember?.bitWidth ?? 0 > 0

View File

@@ -84,7 +84,7 @@ bb0:
sil [no_allocation] [ossa] @deserialize_and_inline_after_devirtualize : $@convention(thin) (@in Int) -> () {
bb0(%0 : $*Int):
%1 = metatype $@thick Int.Type
%2 = witness_method $Int, #Comparable."<" : <Self where Self : Comparable> (Self.Type) -> (Self, Self) -> Bool : $@convention(witness_method: Comparable) <τ_0_0 where τ_0_0 : Comparable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> Bool
%2 = witness_method $Int, #Comparable."<" : <Self where Self : Comparable, Self : ~Copyable> (Self.Type) -> (borrowing Self, borrowing Self) -> Bool : $@convention(witness_method: Comparable) <τ_0_0 where τ_0_0 : Comparable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> Bool
%3 = apply %2<Int>(%0, %0, %1) : $@convention(witness_method: Comparable) <τ_0_0 where τ_0_0 : Comparable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> Bool
%4 = tuple()
return %4 : $()
@@ -124,7 +124,7 @@ bb0(%0 : $*Int, %1 : $*Int, %2 : $@thick Int.Type):
sil_witness_table public_external [serialized] Int: Comparable module Swift {
base_protocol Equatable: Int: Equatable module Swift
method #Comparable."<": <Self where Self : Comparable> (Self.Type) -> (Self, Self) -> Bool : @$sSiSLsSL1loiySbx_xtFZTW
method #Comparable."<": <Self where Self : Comparable, Self : ~Copyable> (Self.Type) -> (borrowing Self, borrowing Self) -> Bool : @$sSiSLsSL1loiySbx_xtFZTW
}
sil [ossa] @get_int_value : $@convention(thin) () -> Int32 {

View File

@@ -71,7 +71,6 @@ Protocol CodingKey has added inherited protocol Copyable
Protocol CodingKey has added inherited protocol Escapable
Protocol Collection has added inherited protocol Copyable
Protocol Collection has added inherited protocol Escapable
Protocol Comparable has added inherited protocol Copyable
Protocol Comparable has added inherited protocol Escapable
Protocol CustomDebugStringConvertible has added inherited protocol Copyable
Protocol CustomDebugStringConvertible has added inherited protocol Escapable
@@ -387,3 +386,18 @@ Func Equatable.==(_:_:) has generic signature change from <Self where Self : Swi
Func Equatable.==(_:_:) has parameter 0 changing from Default to Shared
Func Equatable.==(_:_:) has parameter 1 changing from Default to Shared
// Comparable: ~Copyable
Protocol Comparable has generic signature change from <Self : Swift.Equatable> to <Self : Swift.Equatable, Self : ~Copyable>
Func Comparable.<(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.<(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.<(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.<=(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.<=(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.<=(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.>(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.>(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.>(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.>=(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.>=(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.>=(_:_:) has parameter 1 changing from Default to Shared

View File

@@ -189,7 +189,6 @@ Protocol CodingKey has added inherited protocol Copyable
Protocol CodingKey has added inherited protocol Escapable
Protocol Collection has added inherited protocol Copyable
Protocol Collection has added inherited protocol Escapable
Protocol Comparable has added inherited protocol Copyable
Protocol Comparable has added inherited protocol Escapable
Protocol CustomDebugStringConvertible has added inherited protocol Copyable
Protocol CustomDebugStringConvertible has added inherited protocol Escapable
@@ -892,4 +891,26 @@ Func Equatable.==(_:_:) has generic signature change from <Self where Self : Swi
Func Equatable.==(_:_:) has parameter 0 changing from Default to Shared
Func Equatable.==(_:_:) has parameter 1 changing from Default to Shared
// Comparable: ~Copyable
Protocol Comparable has generic signature change from <Self : Swift.Equatable> to <Self : Swift.Equatable, Self : ~Copyable>
Func Comparable.<(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.<(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.<(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.<=(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.<=(_:_:) has mangled name changing from 'static (extension in Swift):Swift.Comparable.<= infix(A, A) -> Swift.Bool' to 'static (extension in Swift):Swift.Comparable< where A: ~Swift.Copyable>.<= infix(A, A) -> Swift.Bool'
Func Comparable.<=(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.<=(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.<=(_:_:) is now with @_preInverseGenerics
Func Comparable.>(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.>(_:_:) has mangled name changing from 'static (extension in Swift):Swift.Comparable.> infix(A, A) -> Swift.Bool' to 'static (extension in Swift):Swift.Comparable< where A: ~Swift.Copyable>.> infix(A, A) -> Swift.Bool'
Func Comparable.>(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.>(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.>(_:_:) is now with @_preInverseGenerics
Func Comparable.>=(_:_:) has generic signature change from <Self where Self : Swift.Comparable> to <Self where Self : Swift.Comparable, Self : ~Copyable>
Func Comparable.>=(_:_:) has mangled name changing from 'static (extension in Swift):Swift.Comparable.>= infix(A, A) -> Swift.Bool' to 'static (extension in Swift):Swift.Comparable< where A: ~Swift.Copyable>.>= infix(A, A) -> Swift.Bool'
Func Comparable.>=(_:_:) has parameter 0 changing from Default to Shared
Func Comparable.>=(_:_:) has parameter 1 changing from Default to Shared
Func Comparable.>=(_:_:) is now with @_preInverseGenerics
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)

View File

@@ -0,0 +1,85 @@
//===--- NoncopyableComparable.swift - tests for Comparable: ~Copyable -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
import StdlibUnittest
let NoncopyableComparableTests = TestSuite("NoncopyableComparable")
struct Noncopyable<Wrapped: ~Copyable>: ~Copyable {
var wrapped: Wrapped
}
extension Noncopyable: Equatable where Wrapped: Equatable & ~Copyable { }
extension Noncopyable: Comparable where Wrapped: Comparable & ~Copyable {
static func < (lhs: borrowing Self, rhs: borrowing Self) -> Bool { lhs.wrapped < rhs.wrapped }
}
extension Comparable where Self: ~Copyable {
func isLessThan(_ other: borrowing Self) -> Bool {
self < other
}
}
func isLessOrEqual<T: Comparable & ~Copyable>(_ lhs: borrowing T, _ rhs: borrowing T) -> Bool {
lhs <= rhs
}
@available(SwiftStdlib 6.2, *)
extension InlineArray where Element: Comparable & ~Copyable {
func minIndex() -> Int? {
indices.min { self[$0] < self[$1] }
}
func inReverseOrder() -> Bool {
for i in indices.dropFirst() {
if self[i] > self[i-1] { return false }
}
return true
}
}
NoncopyableComparableTests.test("comparing noncopyables") {
let a = Noncopyable(wrapped: 0)
let b = Noncopyable(wrapped: 1)
let c = Noncopyable(wrapped: 2)
expectTrue(a < b)
expectTrue(c > a)
expectFalse(a < a)
expectFalse(a > c)
expectFalse(c <= a)
expectTrue(a <= a)
expectTrue(a < b && b < c)
expectTrue(a.isLessThan(b))
expectTrue(isLessOrEqual(a,b))
expectFalse(b.isLessThan(a))
let nc2 = Noncopyable(wrapped: Noncopyable(wrapped: "1"))
expectFalse(nc2 < nc2)
expectFalse(nc2 > nc2)
expectTrue(nc2 >= nc2)
expectTrue(nc2 < .init(wrapped: .init(wrapped: "2")))
guard #available(SwiftStdlib 6.2, *) else { return }
var array: [3 of Noncopyable] = [c,b,a]
expectTrue(array.minIndex() == 2)
expectTrue(array.inReverseOrder())
array.swapAt(1, 2)
expectFalse(array.inReverseOrder())
}
runAllTests()

View File

@@ -34,14 +34,18 @@ func testAmbiguousStringComparisons(s: String) {
let nsString = s as NSString
let a1 = s as NSString == nsString
let a2 = s as NSString != nsString
let a3 = s < nsString // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{24-24= as String}}
let a3 = s < nsString
// expected-error@-1{{binary operator '<' cannot be applied to operands of type 'String' and 'NSString'}}
// expected-note@-2{{overloads for '<' exist with these partially matching parameter lists: (String, String)}}
let a4 = s <= nsString // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{25-25= as String}}
let a5 = s >= nsString // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{25-25= as String}}
let a6 = s > nsString // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{24-24= as String}}
// now the other way
let a7 = nsString == s as NSString
let a8 = nsString != s as NSString
let a9 = nsString < s // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{20-20= as String}}
let a9 = nsString < s
// expected-error@-1{{binary operator '<' cannot be applied to operands of type 'NSString' and 'String'}}
// expected-note@-2{{overloads for '<' exist with these partially matching parameter lists: (String, String)}}
let a10 = nsString <= s // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{21-21= as String}}
let a11 = nsString >= s // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{21-21= as String}}
let a12 = nsString > s // expected-error{{'NSString' is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert?}} {{21-21= as String}}