Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Correct Swift compile errors for comparison expressions #494

Merged
merged 1 commit into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions Packages/ClientRuntime/Sources/Waiters/JMESUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@ public enum JMESUtils {
return comparator(lhs, rhs)
}

// Functions for comparing Int to Int.
// Functions for comparing Int to Int. Double comparators are used since Int has
// extra overloads on `==` that prevent it from resolving correctly, and Ints compared
// to Doubles are already compared as Doubles anyway.

public static func compare(_ lhs: Int?, _ comparator: (Int?, Int?) -> Bool, _ rhs: Int?) -> Bool {
comparator(lhs, rhs)
public static func compare(_ lhs: Int?, _ comparator: (Double?, Double?) -> Bool, _ rhs: Int?) -> Bool {
comparator(lhs.map { Double($0) }, rhs.map { Double($0) })
}

public static func compare(_ lhs: Int?, _ comparator: (Int, Int) -> Bool, _ rhs: Int?) -> Bool {
public static func compare(_ lhs: Int?, _ comparator: (Double, Double) -> Bool, _ rhs: Int?) -> Bool {
guard let lhs = lhs, let rhs = rhs else { return false }
return comparator(lhs, rhs)
return comparator(Double(lhs), Double(rhs))
}

// Function for comparing String to String.
Expand Down
165 changes: 165 additions & 0 deletions Packages/ClientRuntime/Tests/WaiterTests/JMESUtilsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import XCTest
@testable import ClientRuntime

class JMESUtilsTests: XCTestCase {

// MARK: - Equatable

// Note that Swift has trouble resolving the `!=` operator
// for `Double?`, `Double?` operand types (not for any other type).
// Codegen will translate `!=` into `==` and negate the expression
// to avoid a compile failure.
//
// Hence `Int` (which uses `Double` comparators) and `Double` are
// not tested with `!=` below.

func test_equateInt_handlesOptionalityCombos() async throws {
let lhs: Int? = 1
let rhs: Int? = 2
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs!))
}

func test_equateDouble_handlesOptionalityCombos() async throws {
let lhs: Double? = 1.0
let rhs: Double? = 2.0
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs!))
}

func test_equateString_handlesOptionalityCombos() async throws {
let lhs: String? = "a"
let rhs: String? = "b"
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs, !=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, !=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, !=, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, !=, rhs!))
}

func test_equateBool_handlesOptionalityCombos() async throws {
let lhs: Bool? = true
let rhs: Bool? = false
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs, !=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, !=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, !=, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, !=, rhs!))
}

func test_equateIntToDouble_handlesOptionalityCombos() async throws {
let lhs: Int? = 1
let rhs: Double? = 2.0
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs!))
}

func test_equateStringToRawRepresentable_handlesOptionalityCombos() async throws {
let lhs: String? = "a"
let rhs: Stringed? = Stringed(rawValue: "b")
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, ==, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, ==, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs, !=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, !=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, !=, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, !=, rhs!))
}

// MARK: - Comparable

func test_compareInt_handlesOptionalityCombos() async throws {
let lhs: Int? = 1
let rhs: Int? = 2
XCTAssertTrue(JMESUtils.compare(lhs, <, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, <, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, <, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, <, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs, <=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, <=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, <=, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, <=, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs, >, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, >, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, >, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, >, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs, >=, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, >=, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, >=, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, >=, rhs!))
}

func test_compareDouble_handlesOptionalityCombos() async throws {
let lhs: Double? = 1.0
let rhs: Double? = 2.0
XCTAssertTrue(JMESUtils.compare(lhs, <, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, <, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, <, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, <, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs, <=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, <=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, <=, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, <=, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs, >, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, >, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, >, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, >, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs, >=, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, >=, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, >=, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, >=, rhs!))
}

func test_compareIntToDouble_handlesOptionalityCombos() async throws {
let lhs: Int? = 1
let rhs: Double? = 2.0
XCTAssertTrue(JMESUtils.compare(lhs, <, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, <, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, <, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, <, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs, <=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs!, <=, rhs))
XCTAssertTrue(JMESUtils.compare(lhs, <=, rhs!))
XCTAssertTrue(JMESUtils.compare(lhs!, <=, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs, >, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, >, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, >, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, >, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs, >=, rhs))
XCTAssertFalse(JMESUtils.compare(lhs!, >=, rhs))
XCTAssertFalse(JMESUtils.compare(lhs, >=, rhs!))
XCTAssertFalse(JMESUtils.compare(lhs!, >=, rhs!))
}
}

// Used for tests of Equatable between String and RawRepresentable.
fileprivate struct Stringed: RawRepresentable {
typealias RawValue = String

let rawValue: String

init?(rawValue: String) {
self.rawValue = rawValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import software.amazon.smithy.jmespath.JmespathExpression
import software.amazon.smithy.jmespath.RuntimeType
import software.amazon.smithy.jmespath.ast.AndExpression
import software.amazon.smithy.jmespath.ast.ComparatorExpression
import software.amazon.smithy.jmespath.ast.ComparatorType
import software.amazon.smithy.jmespath.ast.CurrentExpression
import software.amazon.smithy.jmespath.ast.ExpressionTypeExpression
import software.amazon.smithy.jmespath.ast.FieldExpression
Expand Down Expand Up @@ -162,20 +163,29 @@ class JMESPathVisitor(
}

// Perform a comparison of two values.
// The JMESValue type is used to provide conversion and comparison as needed between types
// that aren't comparable in "pure Swift" (i.e. Int to Double or String to RawRepresentable
// by String.)
//
// The JMESUtils.compare() function is used to provide conversion and comparison as needed
// between types that aren't comparable in "pure Swift" (i.e. Int to Double or String to
// RawRepresentable by String.)
//
// The Smithy comparator is a string that just happens to match up with all Swift comparators,
// so it is rendered into Swift as-is.
// so it is rendered into Swift as-is, with one exception:
//
// Due to overload resolution difficulties with `!=` in Swift, an inequality expression is
// rendered as equality then negated.
override fun visitComparator(expression: ComparatorExpression): JMESVariable {
val left = expression.left!!.accept(this)
val right = expression.right!!.accept(this)
val isInequality = expression.comparator == ComparatorType.NOT_EQUAL
val comparator = ComparatorType.EQUAL.takeIf { isInequality } ?: expression.comparator
val negationMark = "!".takeIf { isInequality } ?: ""
val comparisonResultVar = JMESVariable("comparison", false, boolShape)
return addTempVar(
comparisonResultVar,
"JMESUtils.compare(\$L, \$L, \$L)",
"\$LJMESUtils.compare(\$L, \$L, \$L)",
negationMark,
left.name,
expression.comparator,
comparator,
right.name
)
}
Expand Down