Allow Hashable: ~Copyable (#85748)

Builds on #85746 which covers Equatable.
This commit is contained in:
Ben Cohen
2025-12-11 14:47:36 -08:00
committed by GitHub
parent b8e3e4b7b2
commit 9dd9e96e1d
11 changed files with 135 additions and 21 deletions

View File

@@ -37,6 +37,8 @@
#include "swift/AST/Expr.h"
#include "swift/AST/ForeignErrorConvention.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/InFlightSubstitution.h"
#include "swift/AST/KnownProtocols.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/SubstitutionMap.h"
@@ -2430,8 +2432,20 @@ RValue SILGenFunction::emitAnyHashableErasure(SILLocation loc,
return emitUndefRValue(loc, getASTContext().getAnyHashableType());
// Construct the substitution for T: Hashable.
auto subMap = SubstitutionMap::getProtocolSubstitutions(
conformance.getProtocol(), type, conformance);
auto subMap = SubstitutionMap::get(convertFn->getGenericSignature(), type,
[&](InFlightSubstitution &ifs,
Type ty,
ProtocolDecl *proto) -> ProtocolConformanceRef {
switch (*proto->getKnownProtocolKind()) {
case KnownProtocolKind::Hashable:
return conformance;
case KnownProtocolKind::Copyable:
case KnownProtocolKind::Escapable:
return lookupConformance(type, proto);
default:
llvm_unreachable("no other conformances should be involved");
}
});
return emitApplyOfLibraryIntrinsic(loc, convertFn, subMap, value, C);
}

View File

@@ -101,7 +101,7 @@
/// print("New tap detected at (\(nextTap.x), \(nextTap.y)).")
/// }
/// // Prints "New tap detected at (0, 1).")
public protocol Hashable: Equatable {
public protocol Hashable: Equatable & ~Copyable {
/// The hash value.
///
/// Hash values are not guaranteed to be equal across different executions of
@@ -135,9 +135,10 @@ public protocol Hashable: Equatable {
func _rawHashValue(seed: Int) -> Int
}
extension Hashable {
extension Hashable where Self: ~Copyable {
@inlinable
@inline(__always)
@_preInverseGenerics
public func _rawHashValue(seed: Int) -> Int {
var hasher = Hasher(_seed: seed)
hasher.combine(self)
@@ -148,7 +149,8 @@ extension Hashable {
// Called by synthesized `hashValue` implementations.
@inlinable
@inline(__always)
public func _hashValue<H: Hashable>(for value: H) -> Int {
@_preInverseGenerics
public func _hashValue<H: Hashable & ~Copyable>(for value: borrowing H) -> Int {
return value._rawHashValue(seed: 0)
}

View File

@@ -350,7 +350,8 @@ public struct Hasher {
/// - Parameter value: A value to add to the hasher.
@inlinable
@inline(__always)
public mutating func combine<H: Hashable>(_ value: H) {
@_preInverseGenerics
public mutating func combine<H: Hashable & ~Copyable>(_ value: borrowing H) {
value.hash(into: &self)
}

View File

@@ -58,7 +58,7 @@ enum TrailingSemi {
// CHECK-AST-LABEL: (func_decl{{.*}}"generic(_:)" "<T : Hashable>" interface_type="<T where T : Hashable> (T) -> ()" access=internal captures=(<generic> {{.*}})
func generic<T: Hashable>(_: T) {}
// CHECK-AST: (pattern_binding_decl
// CHECK-AST: (processed_init=declref_expr type="(Int) -> ()" location={{.*}} range={{.*}} decl="main.(file).generic@{{.*}} [with (substitution_map generic_signature=<T where T : Hashable> T -> Int)]" function_ref=unapplied))
// CHECK-AST: (processed_init=declref_expr type="(Int) -> ()" location={{.*}} range={{.*}} decl="main.(file).generic@{{.*}} [with (substitution_map generic_signature=<T where T : Copyable, T : Hashable> T -> Int)]" function_ref=unapplied))
let _: (Int) -> () = generic
// Closures should be marked as escaping or not.

View File

@@ -52,7 +52,9 @@ public struct MoveOnlyS1<T> : ~Copyable { /*deinit {}*/ }
public struct MoveOnlyS2<T: Equatable> : ~Copyable { /*deinit {}*/ }
public struct MoveOnlyS3<T: ~Copyable> : ~Copyable { /*deinit {}*/ }
protocol Rope<Element>: Hashable, ~Copyable { // expected-error {{'Self' required to be 'Copyable' but is marked with '~Copyable'}}
protocol CopyHashable { }
protocol Rope<Element>: CopyHashable, ~Copyable { // expected-error {{'Self' required to be 'Copyable' but is marked with '~Copyable'}}
associatedtype Element: ~Copyable
}
@@ -102,8 +104,8 @@ typealias Z4 = ~Rope<Int> // expected-error {{type 'Rope<Int>' cannot be suppres
typealias Z5 = (~Int) -> Void // expected-error {{type 'Int' cannot be suppressed}}
typealias Z6 = ~() -> () // expected-error {{single argument function types require parentheses}}
// expected-error@-1 {{type '()' cannot be suppressed}}
typealias Z7 = ~(Copyable & Hashable) // expected-error {{type 'Hashable' cannot be suppressed}}
typealias Z8 = ~Copyable & Hashable // expected-error {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
typealias Z7 = ~(Copyable & CopyHashable) // expected-error {{type 'CopyHashable' cannot be suppressed}}
typealias Z8 = ~Copyable & CopyHashable // expected-error {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
struct NotAProtocol {}

View File

@@ -83,9 +83,9 @@ extension NoValues: Codable {}
// CHECK-LABEL: sil_witness_table hidden <T where T : Hashable> Enum<T>: Hashable module synthesized_conformance_enum {
// CHECK-DAG: base_protocol Equatable: <T where T : Equatable> Enum<T>: Equatable module synthesized_conformance_enum
// CHECK-DAG: method #Hashable.hashValue!getter: <Self where Self : Hashable> (Self) -> () -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance <A> Enum<A>
// CHECK-DAG: method #Hashable.hash: <Self where Self : Hashable> (Self) -> (inout Hasher) -> () : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance <A> Enum<A>
// CHECK-DAG: method #Hashable._rawHashValue: <Self where Self : Hashable> (Self) -> (Int) -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance <A> Enum<A>
// CHECK-DAG: method #Hashable.hashValue!getter: <Self where Self : Hashable, Self : ~Copyable> (Self) -> () -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance <A> Enum<A>
// CHECK-DAG: method #Hashable.hash: <Self where Self : Hashable, Self : ~Copyable> (Self) -> (inout Hasher) -> () : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance <A> Enum<A>
// CHECK-DAG: method #Hashable._rawHashValue: <Self where Self : Hashable, Self : ~Copyable> (Self) -> (Int) -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance <A> Enum<A>
// CHECK-DAG: conditional_conformance (T: Hashable): dependent
// CHECK: }

View File

@@ -69,9 +69,9 @@ extension Struct: Codable where T: Codable {}
// CHECK-LABEL: sil_witness_table hidden <T where T : Hashable> Struct<T>: Hashable module synthesized_conformance_struct {
// CHECK-DAG: base_protocol Equatable: <T where T : Equatable> Struct<T>: Equatable module synthesized_conformance_struct
// CHECK-DAG: method #Hashable.hashValue!getter: <Self where Self : Hashable> (Self) -> () -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance <A> Struct<A>
// CHECK-DAG: method #Hashable.hash: <Self where Self : Hashable> (Self) -> (inout Hasher) -> () : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance <A> Struct<A>
// CHECK-DAG: method #Hashable._rawHashValue: <Self where Self : Hashable> (Self) -> (Int) -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance <A> Struct<A>
// CHECK-DAG: method #Hashable.hashValue!getter: <Self where Self : Hashable, Self : ~Copyable> (Self) -> () -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance <A> Struct<A>
// CHECK-DAG: method #Hashable.hash: <Self where Self : Hashable, Self : ~Copyable> (Self) -> (inout Hasher) -> () : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance <A> Struct<A>
// CHECK-DAG: method #Hashable._rawHashValue: <Self where Self : Hashable, Self : ~Copyable> (Self) -> (Int) -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance <A> Struct<A>
// CHECK-DAG: conditional_conformance (T: Hashable): dependent
// CHECK: }

View File

@@ -52,11 +52,11 @@ func basic_vararg(_ va: MO...) {} // expected-error {{noncopyable type 'MO' cann
func illegalTypes<T>(_ t: T) {
let _: Array<MO> // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
let _: Maybe<MO> // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
let _: Dictionary<MO, String> // expected-error {{type 'MO' does not conform to protocol 'Hashable'}}
let _: Dictionary<MO, String> // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
let _: [MO] // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
let _: [String : MO] // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
let _: [MO : MO] // expected-error {{type 'MO' does not conform to protocol 'Hashable'}}
let _: [MO : T] // expected-error {{type 'MO' does not conform to protocol 'Hashable'}}
let _: [MO : MO] // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
let _: [MO : T] // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}
_ = t as! ValBox<MO> // expected-error {{type 'MO' does not conform to protocol 'Copyable'}}

View File

@@ -114,7 +114,6 @@ Protocol FixedWidthInteger has added inherited protocol Copyable
Protocol FixedWidthInteger has added inherited protocol Escapable
Protocol FloatingPoint has added inherited protocol Copyable
Protocol FloatingPoint has added inherited protocol Escapable
Protocol Hashable has added inherited protocol Copyable
Protocol Hashable has added inherited protocol Escapable
Protocol Identifiable has added inherited protocol Copyable
Protocol Identifiable has added inherited protocol Escapable
@@ -399,3 +398,10 @@ 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
// Hashable: ~Copyable
Protocol Hashable has generic signature change from <Self : Swift.Equatable> to <Self : Swift.Equatable, Self : ~Copyable>
Accessor Hashable.hashValue.Get() has generic signature change from <Self where Self : Swift.Hashable> to <Self where Self : Swift.Hashable, Self : ~Copyable>
Func Hashable.hash(into:) has generic signature change from <Self where Self : Swift.Hashable> to <Self where Self : Swift.Hashable, Self : ~Copyable>
Func Hasher.combine(_:) has generic signature change from <H where H : Swift.Hashable> to <H where H : Swift.Hashable, H : ~Copyable>
Func Hasher.combine(_:) has parameter 0 changing from Default to Shared

View File

@@ -232,7 +232,6 @@ Protocol FixedWidthInteger has added inherited protocol Copyable
Protocol FixedWidthInteger has added inherited protocol Escapable
Protocol FloatingPoint has added inherited protocol Copyable
Protocol FloatingPoint has added inherited protocol Escapable
Protocol Hashable has added inherited protocol Copyable
Protocol Hashable has added inherited protocol Escapable
Protocol Identifiable has added inherited protocol Copyable
Protocol Identifiable has added inherited protocol Escapable
@@ -911,5 +910,20 @@ 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
// Hashable: ~Copyable
Protocol Hashable has generic signature change from <Self : Swift.Equatable> to <Self : Swift.Equatable, Self : ~Copyable>
Accessor Hashable.hashValue.Get() has generic signature change from <Self where Self : Swift.Hashable> to <Self where Self : Swift.Hashable, Self : ~Copyable>
Func Hashable._rawHashValue(seed:) has generic signature change from <Self where Self : Swift.Hashable> to <Self where Self : Swift.Hashable, Self : ~Copyable>
Func Hashable._rawHashValue(seed:) has mangled name changing from '(extension in Swift):Swift.Hashable._rawHashValue(seed: Swift.Int) -> Swift.Int' to '(extension in Swift):Swift.Hashable< where A: ~Swift.Copyable>._rawHashValue(seed: Swift.Int) -> Swift.Int'
Func Hashable._rawHashValue(seed:) is now with @_preInverseGenerics
Func Hashable.hash(into:) has generic signature change from <Self where Self : Swift.Hashable> to <Self where Self : Swift.Hashable, Self : ~Copyable>
Func Hasher.combine(_:) has generic signature change from <H where H : Swift.Hashable> to <H where H : Swift.Hashable, H : ~Copyable>
Func Hasher.combine(_:) has mangled name changing from 'Swift.Hasher.combine<A where A: Swift.Hashable>(A) -> ()' to 'Swift.Hasher.combine<A where A: Swift.Hashable, A: ~Swift.Copyable>(A) -> ()'
Func Hasher.combine(_:) has parameter 0 changing from Default to Shared
Func Hasher.combine(_:) is now with @_preInverseGenerics
Func _hashValue(for:) has generic signature change from <H where H : Swift.Hashable> to <H where H : Swift.Hashable, H : ~Copyable>
Func _hashValue(for:) has mangled name changing from 'Swift._hashValue<A where A: Swift.Hashable>(for: A) -> Swift.Int' to 'Swift._hashValue<A where A: Swift.Hashable, A: ~Swift.Copyable>(for: A) -> Swift.Int'
Func _hashValue(for:) has parameter 0 changing from Default to Shared
Func _hashValue(for:) is now with @_preInverseGenerics
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)

View File

@@ -0,0 +1,75 @@
//===--- NoncopyableHashable.swift - tests for Hashable: ~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 NoncopyableHashableTests = TestSuite("NoncopyableHashable")
struct Noncopyable<Wrapped: ~Copyable>: ~Copyable {
var wrapped: Wrapped
}
extension Noncopyable: Equatable where Wrapped: Equatable & ~Copyable { }
extension Noncopyable: Hashable where Wrapped: Hashable & ~Copyable { }
extension Hashable where Self: ~Copyable {
func sameHash(as other: borrowing Self) -> Bool {
self.hashValue == other.hashValue
}
}
func differentHash<T: Hashable & ~Copyable>(_ lhs: borrowing T, _ rhs: borrowing T) -> Bool {
lhs.hashValue != rhs.hashValue
}
@available(SwiftStdlib 6.2, *)
extension InlineArray where Element: Hashable & ~Copyable {
func combinedHashes() -> Int {
var hasher = Hasher()
for i in self.indices {
self[i].hash(into: &hasher)
}
return hasher.finalize()
}
}
NoncopyableHashableTests.test("hashing noncopyables") {
let a = Noncopyable(wrapped: 1)
let b = Noncopyable(wrapped: 2)
let c = Noncopyable(wrapped: 1)
expectTrue(a.sameHash(as: a))
expectFalse(a.sameHash(as: b))
expectTrue(a.sameHash(as: c))
expectTrue(differentHash(a,b))
expectFalse(differentHash(a,a))
expectFalse(differentHash(a,c))
let nc2 = Noncopyable(wrapped: Noncopyable(wrapped: "1"))
expectTrue(nc2.sameHash(as: nc2))
expectTrue(differentHash(nc2, .init(wrapped: .init(wrapped: "2"))))
guard #available(SwiftStdlib 6.2, *) else { return }
let a1: [_ of _] = [a,b]
let d = Noncopyable(wrapped: 2)
let a2: [_ of _] = [c,d]
expectEqual(a1.combinedHashes(), a2.combinedHashes())
}
runAllTests()