Skip to content

Commit

Permalink
Merge pull request #408 from kballard/tuple-comparison-ops
Browse files Browse the repository at this point in the history
Implement tuple comparison operators
  • Loading branch information
Dave Abrahams committed Dec 24, 2015
2 parents 8c839b6 + b61c7a5 commit 3576996
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 9 deletions.
1 change: 1 addition & 0 deletions stdlib/public/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ set(SWIFTLIB_SOURCES
Mirror.swift
Process.swift
SliceBuffer.swift
Tuple.swift.gyb
VarArgs.swift
Zip.swift
Prespecialized.swift
Expand Down
55 changes: 55 additions & 0 deletions stdlib/public/core/Tuple.swift.gyb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

// Generate comparison functions for tuples up to some reasonable arity.

% for arity in range(2,7):
% typeParams = [chr(ord("A")+i) for i in range(arity)]
% tupleT = "({})".format(",".join(typeParams))

% equatableTypeParams = ", ".join(["{} : Equatable".format(c) for c in typeParams])

/// Returns `true` iff each component of `lhs` is equal to the corresponding
/// component of `rhs`.
@warn_unused_result
public func == <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
% ops = ["lhs.{} == rhs.{}".format(i,i) for i in range(arity)]
return ${" && ".join(ops)}
}

/// Returns `true` iff any component of `lhs` is not equal to the corresponding
/// component of `rhs`.
@warn_unused_result
public func != <${equatableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
% ops = ["lhs.{} != rhs.{}".format(i,i) for i in range(arity)]
return ${" || ".join(ops)}
}

% comparableTypeParams = ", ".join(["{} : Comparable".format(c) for c in typeParams])
% for op in ["<", ">"]:
% for opeq in ["", "="]:
/// A [lexicographical order](https://en.wikipedia.org/wiki/Lexicographical_order)
/// over tuples of `Comparable` elements.
///
/// Given two tuples `(a1,a2,…,aN)` and `(b1,b2,…,bN)`, the first tuple is
/// `${op}${opeq}` the second tuple iff `a1 ${op} b1` or (`a1 == b1` and
/// `(a2,…,aN) ${op}${opeq} (b2,…,bN)`).
@warn_unused_result
public func ${op}${opeq} <${comparableTypeParams}>(lhs: ${tupleT}, rhs: ${tupleT}) -> Bool {
% for i in range(arity-1):
if lhs.${i} != rhs.${i} { return lhs.${i} ${op} rhs.${i} }
% end
return lhs.${arity-1} ${op}${opeq} rhs.${arity-1}
}
% end
% end
% end
154 changes: 154 additions & 0 deletions test/1_stdlib/Tuple.swift.gyb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// RUN: rm -f %t.swift %t.out

// RUN: %S/../../utils/gyb %s -o %t.swift
// RUN: %S/../../utils/line-directive %t.swift -- %target-build-swift %t.swift -o %t.out
// RUN: %S/../../utils/line-directive %t.swift -- %target-run %t.out
// REQUIRES: executable_test

import StdlibUnittest

var TupleTestSuite = TestSuite("Tuple")

// Test tuple comparison operators
// all the tuple types use the same basic implementation for the operators
// so testing any arity tests the logic for them all.
// Include at least one invocation for all arities as a sanity check.

% maxArity = 6 # the highest arity the operators are defined for

func testEquality<A : Equatable, B : Equatable, C : Equatable>(
lhs: (A,B,C), equal: Bool, to rhs: (A,B,C),
//===--- TRACE boilerplate ----------------------------------------------===//
@autoclosure _ message: ()->String = "",
showFrame: Bool = true,
stackTrace: SourceLocStack = SourceLocStack(),
file: String = __FILE__, line: UInt = __LINE__
) {
let trace = stackTrace.pushIf(showFrame, file: file, line: line)
expectEqual(equal, lhs == rhs, stackTrace: trace)
expectEqual(equal, rhs == lhs, stackTrace: trace)
expectEqual(!equal, lhs != rhs, stackTrace: trace)
expectEqual(!equal, rhs != lhs, stackTrace: trace)
}

TupleTestSuite.test("Tuple/equality") {
testEquality((1,2,3), equal: true, to: (1,2,3))
testEquality((1,2,3), equal: false, to: (1,2,4))
testEquality((1,2,3), equal: false, to: (1,3,3))
testEquality((1,2,3), equal: false, to: (2,2,3))
testEquality((1,"2",3), equal: true, to: (1,"2",3))
testEquality((1,"2",3), equal: false, to: (1,"3",3))
testEquality(("one", 2.2, 3..<5), equal: true, to: ("one", 2.2, 3..<5))

testEquality((1.0, 2.0, 3.0), equal: false, to: (1.0, 2.0, .NaN))
testEquality((1.0, 2.0, 3.0), equal: false, to: (1.0, .NaN, 3.0))
testEquality((1.0, 2.0, 3.0), equal: false, to: (.NaN, 2.0, 3.0))
testEquality((1.0, 2.0, 3.0), equal: false, to: (.NaN, .NaN, .NaN))
testEquality((1.0, 2.0, Float.NaN), equal: false, to: (1.0, 2.0, 3.0))
testEquality((1.0, 2.0, Float.NaN), equal: false, to: (1.0, 2.0, Float.NaN))
testEquality((Float.NaN, Float.NaN, Float.NaN), equal: false, to: (.NaN, .NaN, .NaN))
testEquality((Float.NaN, Float.NaN, Float.NaN), equal: false, to: (1.0, 2.0, 3.0))

expectTrue((1,2) == (1,2))
expectTrue((1,2) != (1,3))
expectTrue((1,2,3,4) == (1,2,3,4))
expectTrue((1,2,3,4) != (1,2,3,3))
expectTrue((1,2,3,4,5) == (1,2,3,4,5))
expectTrue((1,2,3,4,5) != (1,2,3,4,4))
expectTrue((1,2,3,4,5,6) == (1,2,3,4,5,6))
expectTrue((1,2,3,4,5,6) != (1,2,3,4,5,5))
}

TupleTestSuite.test("Tuple/equality/sanity-check") {
// sanity check all arities
% for arity in range(2,maxArity+1):
% a = str(tuple(range(1, arity+1)))
% b = "({}, 0)".format(", ".join([str(i) for i in range(1,arity)]))
% c = "(0, {})".format(", ".join([str(i) for i in range(2,arity+1)]))
expectTrue(${a} == ${a})
expectTrue(${a} != ${b})
expectTrue(${b} != ${a})
expectTrue(${a} != ${c})
expectTrue(${c} != ${a})
% end
}

enum Ordering : Equatable {
case LessThan
case EqualTo
case GreaterThan
case UnorderedWith // Comparable defines strict total order, but Float disobeys that with NaN

var isLT: Bool {
return self == .LessThan
}
var isEQ: Bool {
return self == .EqualTo
}
var isGT: Bool {
return self == .GreaterThan
}
}

func testOrdering<A : Comparable, B : Comparable, C : Comparable>(
lhs: (A,B,C), _ ordering: Ordering, _ rhs: (A, B, C),
//===--- TRACE boilerplate ----------------------------------------------===//
@autoclosure _ message: ()->String = "",
showFrame: Bool = true,
stackTrace: SourceLocStack = SourceLocStack(),
file: String = __FILE__, line: UInt = __LINE__
) {
let trace = stackTrace.pushIf(showFrame, file: file, line: line)
expectEqual(ordering.isLT, lhs < rhs, stackTrace: trace)
expectEqual(ordering.isLT, rhs > lhs, stackTrace: trace)
expectEqual(ordering.isLT || ordering.isEQ, lhs <= rhs, stackTrace: trace)
expectEqual(ordering.isLT || ordering.isEQ, rhs >= lhs, stackTrace: trace)
expectEqual(ordering.isGT, lhs > rhs, stackTrace: trace)
expectEqual(ordering.isGT, rhs < lhs, stackTrace: trace)
expectEqual(ordering.isGT || ordering.isEQ, lhs >= rhs, stackTrace: trace)
expectEqual(ordering.isGT || ordering.isEQ, rhs <= lhs, stackTrace: trace)
}

TupleTestSuite.test("Tuple/comparison") {
testOrdering((1,2,3), .EqualTo, (1,2,3))
testOrdering((1,2,3), .LessThan, (1,2,4))
testOrdering((1,2,3), .GreaterThan, (1,2,2))
testOrdering((1,3,2), .GreaterThan, (1,2,3))
testOrdering((0,2,3), .LessThan, (1,2,3))
testOrdering((3,2,1), .GreaterThan, (1,2,3))

testOrdering(("one", 2, 3.3), .EqualTo, ("one", 2, 3.3))
testOrdering(("one", 2, 3.3), .LessThan, ("one", 2, 3.4))
testOrdering(("on", 2, 3.3), .LessThan, ("one", 1, 3.2))

testOrdering((1, 2, Float.NaN), .UnorderedWith, (1, 2, .NaN))
testOrdering((1, Float.NaN, 3), .UnorderedWith, (1, 2, 3))
testOrdering((Double.NaN, 2, 3), .UnorderedWith, (.NaN, 2, 3))
testOrdering((Float.NaN, Float.NaN, Float.NaN), .UnorderedWith, (1, 2, 3))
testOrdering((1, 2, 3.0), .UnorderedWith, (1, 2, .NaN))
testOrdering((1, 2, 3.0), .LessThan, (1, 3, .NaN))
testOrdering((1, 2, 3.0), .GreaterThan, (1, 1, .NaN))
testOrdering((1, 2.0, 3), .LessThan, (2, .NaN, 3))
testOrdering((1, 2.0, 3), .GreaterThan, (0, .NaN, 3))
testOrdering((1, 2, Float.NaN), .LessThan, (1, 3, 3.0))
testOrdering((1, Float.NaN, 3), .GreaterThan, (0, 2.0, 3))
testOrdering(("one", "two", 3.0), .GreaterThan, ("a", "b", .NaN))
testOrdering(("one", "two", .NaN), .GreaterThan, ("a", "b", 3.0))
testOrdering((1.0, "two", "three"), .UnorderedWith, (.NaN, "two", "four"))
testOrdering((.NaN, "two", "three"), .UnorderedWith, (1.0, "two", "four"))
}

TupleTestSuite.test("Tuple/comparison/sanity-check") {
// sanity check all arities
% for arity in range(2,maxArity+1):
% a = str(tuple(range(1, arity+1)))
% b = "({}, 0)".format(", ".join([str(i) for i in range(1,arity)]))
% c = "(0, {})".format(", ".join([str(i) for i in range(2,arity+1)]))
expectTrue(${b} < ${a})
expectTrue(${b} <= ${a})
expectTrue(${a} > ${c})
expectTrue(${a} >= ${c})
% end
}

runAllTests()
36 changes: 27 additions & 9 deletions test/IDE/complete_expr_tuple.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,45 @@ func testTupleNoDot1() {
var t = (1, 2.0)
t#^TUPLE_NO_DOT_1^#
}
// TUPLE_NO_DOT_1: Begin completions, 2 items
// TUPLE_NO_DOT_1-NEXT: Pattern/CurrNominal: .0[#Int#]{{; name=.+$}}
// TUPLE_NO_DOT_1-NEXT: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
// TUPLE_NO_DOT_1: Begin completions, 8 items
// TUPLE_NO_DOT_1-DAG: Pattern/CurrNominal: .0[#Int#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: <= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: >= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: < {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_1-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: > {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_1-NEXT: End completions

func testTupleNoDot2() {
var t = (foo: 1, bar: 2.0)
t#^TUPLE_NO_DOT_2^#
}
// TUPLE_NO_DOT_2: Begin completions, 2 items
// TUPLE_NO_DOT_2-NEXT: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
// TUPLE_NO_DOT_2-NEXT: Pattern/CurrNominal: .bar[#Double#]{{; name=.+$}}
// TUPLE_NO_DOT_2: Begin completions, 8 items
// TUPLE_NO_DOT_2-DAG: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Pattern/CurrNominal: .bar[#Double#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: <= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: >= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: < {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_2-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: > {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_2-NEXT: End completions

func testTupleNoDot3() {
var t = (foo: 1, 2.0)
t#^TUPLE_NO_DOT_3^#
}
// TUPLE_NO_DOT_3: Begin completions, 2 items
// TUPLE_NO_DOT_3-NEXT: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
// TUPLE_NO_DOT_3-NEXT: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
// TUPLE_NO_DOT_3: Begin completions, 8 items
// TUPLE_NO_DOT_3-DAG: Pattern/CurrNominal: .foo[#Int#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Pattern/CurrNominal: .1[#Double#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: == {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: <= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: >= {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: < {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: != {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_3-DAG: Decl[InfixOperatorFunction]/OtherModule[Swift]: > {#(Int, Double)#}[#Bool#]{{; name=.+$}}
// TUPLE_NO_DOT_3-NEXT: End completions

func testTupleDot1() {
Expand Down

0 comments on commit 3576996

Please sign in to comment.