Skip to content

Commit b61c7a5

Browse files
committed
[Stdlib] Implement comparison operators for tuples
Implement == and != for tuples up to arity 6 where each component type is Equatable. Implement <, <=, >, and >= for tuples up to arity 6 where each component type is Comparable.
1 parent 6528ec2 commit b61c7a5

File tree

4 files changed

+237
-9
lines changed

4 files changed

+237
-9
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ set(SWIFTLIB_SOURCES
127127
Mirror.swift
128128
Process.swift
129129
SliceBuffer.swift
130+
Tuple.swift.gyb
130131
VarArgs.swift
131132
Zip.swift
132133
Prespecialized.swift

stdlib/public/core/Tuple.swift.gyb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
// Generate comparison functions for tuples up to some reasonable arity.
14+
15+
% for arity in range(2,7):
16+
% typeParams = [chr(ord("A")+i) for i in range(arity)]
17+
% tupleT = "({})".format(",".join(typeParams))
18+
19+
% equatableTypeParams = ", ".join(["{} : Equatable".format(c) for c in typeParams])
20+
21+
/// Returns `true` iff each component of `lhs` is equal to the corresponding
22+
/// component of `rhs`.
23+
@warn_unused_result
24+
public func == <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
25+
% ops = ["lhs.{} == rhs.{}".format(i,i) for i in range(arity)]
26+
return ${" && ".join(ops)}
27+
}
28+
29+
/// Returns `true` iff any component of `lhs` is not equal to the corresponding
30+
/// component of `rhs`.
31+
@warn_unused_result
32+
public func != <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
33+
% ops = ["lhs.{} != rhs.{}".format(i,i) for i in range(arity)]
34+
return ${" || ".join(ops)}
35+
}
36+
37+
% comparableTypeParams = ", ".join(["{} : Comparable".format(c) for c in typeParams])
38+
% for op in ["<", ">"]:
39+
% for opeq in ["", "="]:
40+
/// A [lexicographical order](https://en.wikipedia.org/wiki/Lexicographical_order)
41+
/// over tuples of `Comparable` elements.
42+
///
43+
/// Given two tuples `(a1,a2,…,aN)` and `(b1,b2,…,bN)`, the first tuple is
44+
/// `${op}${opeq}` the second tuple iff `a1 ${op} b1` or (`a1 == b1` and
45+
/// `(a2,…,aN) ${op}${opeq} (b2,…,bN)`).
46+
@warn_unused_result
47+
public func ${op}${opeq} <${comparableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
48+
% for i in range(arity-1):
49+
if lhs.${i} != rhs.${i} { return lhs.${i} ${op} rhs.${i} }
50+
% end
51+
return lhs.${arity-1} ${op}${opeq} rhs.${arity-1}
52+
}
53+
% end
54+
% end
55+
% end

test/1_stdlib/Tuple.swift.gyb

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// RUN: rm -f %t.swift %t.out
2+
3+
// RUN: %S/../../utils/gyb %s -o %t.swift
4+
// RUN: %S/../../utils/line-directive %t.swift -- %target-build-swift %t.swift -o %t.out
5+
// RUN: %S/../../utils/line-directive %t.swift -- %target-run %t.out
6+
// REQUIRES: executable_test
7+
8+
import StdlibUnittest
9+
10+
var TupleTestSuite = TestSuite("Tuple")
11+
12+
// Test tuple comparison operators
13+
// all the tuple types use the same basic implementation for the operators
14+
// so testing any arity tests the logic for them all.
15+
// Include at least one invocation for all arities as a sanity check.
16+
17+
% maxArity = 6 # the highest arity the operators are defined for
18+
19+
func testEquality<A : Equatable, B : Equatable, C : Equatable>(
20+
lhs: (A,B,C), equal: Bool, to rhs: (A,B,C),
21+
//===--- TRACE boilerplate ----------------------------------------------===//
22+
@autoclosure _ message: ()->String = "",
23+
showFrame: Bool = true,
24+
stackTrace: SourceLocStack = SourceLocStack(),
25+
file: String = __FILE__, line: UInt = __LINE__
26+
) {
27+
let trace = stackTrace.pushIf(showFrame, file: file, line: line)
28+
expectEqual(equal, lhs == rhs, stackTrace: trace)
29+
expectEqual(equal, rhs == lhs, stackTrace: trace)
30+
expectEqual(!equal, lhs != rhs, stackTrace: trace)
31+
expectEqual(!equal, rhs != lhs, stackTrace: trace)
32+
}
33+
34+
TupleTestSuite.test("Tuple/equality") {
35+
testEquality((1,2,3), equal: true, to: (1,2,3))
36+
testEquality((1,2,3), equal: false, to: (1,2,4))
37+
testEquality((1,2,3), equal: false, to: (1,3,3))
38+
testEquality((1,2,3), equal: false, to: (2,2,3))
39+
testEquality((1,"2",3), equal: true, to: (1,"2",3))
40+
testEquality((1,"2",3), equal: false, to: (1,"3",3))
41+
testEquality(("one", 2.2, 3..<5), equal: true, to: ("one", 2.2, 3..<5))
42+
43+
testEquality((1.0, 2.0, 3.0), equal: false, to: (1.0, 2.0, .NaN))
44+
testEquality((1.0, 2.0, 3.0), equal: false, to: (1.0, .NaN, 3.0))
45+
testEquality((1.0, 2.0, 3.0), equal: false, to: (.NaN, 2.0, 3.0))
46+
testEquality((1.0, 2.0, 3.0), equal: false, to: (.NaN, .NaN, .NaN))
47+
testEquality((1.0, 2.0, Float.NaN), equal: false, to: (1.0, 2.0, 3.0))
48+
testEquality((1.0, 2.0, Float.NaN), equal: false, to: (1.0, 2.0, Float.NaN))
49+
testEquality((Float.NaN, Float.NaN, Float.NaN), equal: false, to: (.NaN, .NaN, .NaN))
50+
testEquality((Float.NaN, Float.NaN, Float.NaN), equal: false, to: (1.0, 2.0, 3.0))
51+
52+
expectTrue((1,2) == (1,2))
53+
expectTrue((1,2) != (1,3))
54+
expectTrue((1,2,3,4) == (1,2,3,4))
55+
expectTrue((1,2,3,4) != (1,2,3,3))
56+
expectTrue((1,2,3,4,5) == (1,2,3,4,5))
57+
expectTrue((1,2,3,4,5) != (1,2,3,4,4))
58+
expectTrue((1,2,3,4,5,6) == (1,2,3,4,5,6))
59+
expectTrue((1,2,3,4,5,6) != (1,2,3,4,5,5))
60+
}
61+
62+
TupleTestSuite.test("Tuple/equality/sanity-check") {
63+
// sanity check all arities
64+
% for arity in range(2,maxArity+1):
65+
% a = str(tuple(range(1, arity+1)))
66+
% b = "({}, 0)".format(", ".join([str(i) for i in range(1,arity)]))
67+
% c = "(0, {})".format(", ".join([str(i) for i in range(2,arity+1)]))
68+
expectTrue(${a} == ${a})
69+
expectTrue(${a} != ${b})
70+
expectTrue(${b} != ${a})
71+
expectTrue(${a} != ${c})
72+
expectTrue(${c} != ${a})
73+
% end
74+
}
75+
76+
enum Ordering : Equatable {
77+
case LessThan
78+
case EqualTo
79+
case GreaterThan
80+
case UnorderedWith // Comparable defines strict total order, but Float disobeys that with NaN
81+
82+
var isLT: Bool {
83+
return self == .LessThan
84+
}
85+
var isEQ: Bool {
86+
return self == .EqualTo
87+
}
88+
var isGT: Bool {
89+
return self == .GreaterThan
90+
}
91+
}
92+
93+
func testOrdering<A : Comparable, B : Comparable, C : Comparable>(
94+
lhs: (A,B,C), _ ordering: Ordering, _ rhs: (A, B, C),
95+
//===--- TRACE boilerplate ----------------------------------------------===//
96+
@autoclosure _ message: ()->String = "",
97+
showFrame: Bool = true,
98+
stackTrace: SourceLocStack = SourceLocStack(),
99+
file: String = __FILE__, line: UInt = __LINE__
100+
) {
101+
let trace = stackTrace.pushIf(showFrame, file: file, line: line)
102+
expectEqual(ordering.isLT, lhs < rhs, stackTrace: trace)
103+
expectEqual(ordering.isLT, rhs > lhs, stackTrace: trace)
104+
expectEqual(ordering.isLT || ordering.isEQ, lhs <= rhs, stackTrace: trace)
105+
expectEqual(ordering.isLT || ordering.isEQ, rhs >= lhs, stackTrace: trace)
106+
expectEqual(ordering.isGT, lhs > rhs, stackTrace: trace)
107+
expectEqual(ordering.isGT, rhs < lhs, stackTrace: trace)
108+
expectEqual(ordering.isGT || ordering.isEQ, lhs >= rhs, stackTrace: trace)
109+
expectEqual(ordering.isGT || ordering.isEQ, rhs <= lhs, stackTrace: trace)
110+
}
111+
112+
TupleTestSuite.test("Tuple/comparison") {
113+
testOrdering((1,2,3), .EqualTo, (1,2,3))
114+
testOrdering((1,2,3), .LessThan, (1,2,4))
115+
testOrdering((1,2,3), .GreaterThan, (1,2,2))
116+
testOrdering((1,3,2), .GreaterThan, (1,2,3))
117+
testOrdering((0,2,3), .LessThan, (1,2,3))
118+
testOrdering((3,2,1), .GreaterThan, (1,2,3))
119+
120+
testOrdering(("one", 2, 3.3), .EqualTo, ("one", 2, 3.3))
121+
testOrdering(("one", 2, 3.3), .LessThan, ("one", 2, 3.4))
122+
testOrdering(("on", 2, 3.3), .LessThan, ("one", 1, 3.2))
123+
124+
testOrdering((1, 2, Float.NaN), .UnorderedWith, (1, 2, .NaN))
125+
testOrdering((1, Float.NaN, 3), .UnorderedWith, (1, 2, 3))
126+
testOrdering((Double.NaN, 2, 3), .UnorderedWith, (.NaN, 2, 3))
127+
testOrdering((Float.NaN, Float.NaN, Float.NaN), .UnorderedWith, (1, 2, 3))
128+
testOrdering((1, 2, 3.0), .UnorderedWith, (1, 2, .NaN))
129+
testOrdering((1, 2, 3.0), .LessThan, (1, 3, .NaN))
130+
testOrdering((1, 2, 3.0), .GreaterThan, (1, 1, .NaN))
131+
testOrdering((1, 2.0, 3), .LessThan, (2, .NaN, 3))
132+
testOrdering((1, 2.0, 3), .GreaterThan, (0, .NaN, 3))
133+
testOrdering((1, 2, Float.NaN), .LessThan, (1, 3, 3.0))
134+
testOrdering((1, Float.NaN, 3), .GreaterThan, (0, 2.0, 3))
135+
testOrdering(("one", "two", 3.0), .GreaterThan, ("a", "b", .NaN))
136+
testOrdering(("one", "two", .NaN), .GreaterThan, ("a", "b", 3.0))
137+
testOrdering((1.0, "two", "three"), .UnorderedWith, (.NaN, "two", "four"))
138+
testOrdering((.NaN, "two", "three"), .UnorderedWith, (1.0, "two", "four"))
139+
}
140+
141+
TupleTestSuite.test("Tuple/comparison/sanity-check") {
142+
// sanity check all arities
143+
% for arity in range(2,maxArity+1):
144+
% a = str(tuple(range(1, arity+1)))
145+
% b = "({}, 0)".format(", ".join([str(i) for i in range(1,arity)]))
146+
% c = "(0, {})".format(", ".join([str(i) for i in range(2,arity+1)]))
147+
expectTrue(${b} < ${a})
148+
expectTrue(${b} <= ${a})
149+
expectTrue(${a} > ${c})
150+
expectTrue(${a} >= ${c})
151+
% end
152+
}
153+
154+
runAllTests()

test/IDE/complete_expr_tuple.swift

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,45 @@ func testTupleNoDot1() {
2727
var t = (1, 2.0)
2828
t#^TUPLE_NO_DOT_1^#
2929
}
30-
// TUPLE_NO_DOT_1: Begin completions, 2 items
31-
// TUPLE_NO_DOT_1-NEXT: Pattern/CurrNominal: .0[#Int#]{{; name=.+$}}
32-
// TUPLE_NO_DOT_1-NEXT: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
30+
// TUPLE_NO_DOT_1: Begin completions, 8 items
31+
// TUPLE_NO_DOT_1-DAG: Pattern/CurrNominal: .0[#Int#]{{; name=.+$}}
32+
// TUPLE_NO_DOT_1-DAG: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
33+
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#(Int, Double)#}[#Bool#]{{; name=.+$}}
34+
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: <= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
35+
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: >= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
36+
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: < {#(Int, Double)#}[#Bool#]{{; name=.+$}}
37+
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#(Int, Double)#}[#Bool#]{{; name=.+$}}
38+
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: > {#(Int, Double)#}[#Bool#]{{; name=.+$}}
3339
// TUPLE_NO_DOT_1-NEXT: End completions
3440

3541
func testTupleNoDot2() {
3642
var t = (foo: 1, bar: 2.0)
3743
t#^TUPLE_NO_DOT_2^#
3844
}
39-
// TUPLE_NO_DOT_2: Begin completions, 2 items
40-
// TUPLE_NO_DOT_2-NEXT: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
41-
// TUPLE_NO_DOT_2-NEXT: Pattern/CurrNominal: .bar[#Double#]{{; name=.+$}}
45+
// TUPLE_NO_DOT_2: Begin completions, 8 items
46+
// TUPLE_NO_DOT_2-DAG: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
47+
// TUPLE_NO_DOT_2-DAG: Pattern/CurrNominal: .bar[#Double#]{{; name=.+$}}
48+
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#(Int, Double)#}[#Bool#]{{; name=.+$}}
49+
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: <= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
50+
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: >= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
51+
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: < {#(Int, Double)#}[#Bool#]{{; name=.+$}}
52+
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#(Int, Double)#}[#Bool#]{{; name=.+$}}
53+
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: > {#(Int, Double)#}[#Bool#]{{; name=.+$}}
4254
// TUPLE_NO_DOT_2-NEXT: End completions
4355

4456
func testTupleNoDot3() {
4557
var t = (foo: 1, 2.0)
4658
t#^TUPLE_NO_DOT_3^#
4759
}
48-
// TUPLE_NO_DOT_3: Begin completions, 2 items
49-
// TUPLE_NO_DOT_3-NEXT: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
50-
// TUPLE_NO_DOT_3-NEXT: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
60+
// TUPLE_NO_DOT_3: Begin completions, 8 items
61+
// TUPLE_NO_DOT_3-DAG: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
62+
// TUPLE_NO_DOT_3-DAG: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
63+
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#(Int, Double)#}[#Bool#]{{; name=.+$}}
64+
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: <= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
65+
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: >= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
66+
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: < {#(Int, Double)#}[#Bool#]{{; name=.+$}}
67+
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#(Int, Double)#}[#Bool#]{{; name=.+$}}
68+
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: > {#(Int, Double)#}[#Bool#]{{; name=.+$}}
5169
// TUPLE_NO_DOT_3-NEXT: End completions
5270

5371
func testTupleDot1() {

0 commit comments

Comments
 (0)