Skip to content
New issue

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

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

Already on GitHub? # to your account

beIdenticalTo / ===: disallow comparing non-objects #955

Merged
merged 1 commit into from
Feb 25, 2022

Conversation

NachoSoto
Copy link
Contributor

@NachoSoto NachoSoto commented Jan 10, 2022

Equality comparisons don't make sense for anything other than objects, and they will always fail.
Using === with structs for example is likely a programmer error, so to avoid it, a compile-time error is better.

This is potentially a breaking change.

Equality comparisons don't make sense for anything other than objects, and they will always fail.
Using `===` with `struct`s for example is likely a programmer error, so to avoid it, a compile time error is better.

This is potentially a breaking change.
@@ -41,7 +41,6 @@ final class BeIdenticalToTest: XCTestCase {
let value = NSDate()
expect(value).to(be(value))
expect(1 as NSNumber).toNot(be("turtles" as NSString))
expect([1]).toNot(be([1]))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is illegal now. The NSArray equivalent is in the next line.

@NachoSoto
Copy link
Contributor Author

NachoSoto commented Jan 29, 2022

Any thoughts on this @ikesyo? I was just bitten by it again comparing 2 structs with === instead of ==:

Screen Shot 2022-01-29 at 15 47 53

@ikesyo ikesyo added this to the v10 milestone Feb 25, 2022
Copy link
Member

@ikesyo ikesyo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely makes sense 👍

@ikesyo ikesyo merged commit 881fd88 into Quick:main Feb 25, 2022
NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Mar 9, 2022
These were no longer valid since Quick/Nimble#955
@NachoSoto NachoSoto deleted the equivalent-struct branch March 9, 2022 21:21
NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Mar 9, 2022
The `Package.resolved` file is not backwards compatible. We only have test SPM dependencies, so by `.gitignore`ing it we can support both Xcode versions.

Also fixed invalid Nimble usages of `===` that were no longer valid since Quick/Nimble#955.
@acecilia
Copy link
Contributor

acecilia commented Jul 26, 2022

Hi 👋 After this change I am experiencing that below code that previously worked fine, now it does not compile:

protocol MyProtocol { }

final class MyClass: MyProtocol {
    init() { }
}

final class Spec: QuickSpec {
    override func spec() {
        it("success") {
            let object1 = MyClass()
            let object2: MyProtocol = object1
            expect(object2).to(be(object1))
        }

        it("fails") {
            let object1 = MyClass()
            let object2: MyProtocol = MyClass()
            expect(object2).to(be(object1))
        }
    }
}

I understand that there are multiple ways to make these tests compile:

  • Make MyProtocol conform to AnyObject
  • Cast the object with type MyProtocol to the concrete class MyClass: expect(object2 as? MyClass).to(be(object1))

But I am not sure any of above solutions are correct:

  • Make MyProtocol conform to AnyObject: this may not be possible, as there can be usecases where structs conform to the protocol
  • Cast the object with type MyProtocol to the concrete class MyClass: in this case I would prefer if is Nimble itself the one doing the casting and failing the test. Casting to a type that is already in the right part of the expectation seems unnecessary and repetitive

I would consider above code snippet correct: it should build and run as expected without further modifications. Wdyt?

I believe the issue in this case boils down to this: the compiler is not able to tell at compile time that a type (in this case a protocol) is a class

@NachoSoto
Copy link
Contributor Author

I believe the issue in this case boils down to this: the compiler is not able to tell at compile time that a type (in this case a protocol) is a class

That's exactly right. And without that, === can't really work.

@acecilia
Copy link
Contributor

acecilia commented Jul 26, 2022

I believe the issue in this case boils down to this: the compiler is not able to tell at compile time that a type (in this case a protocol) is a class

That's exactly right. And without that, === can't really work.

=== used to work by casting and performing the check at runtime.

The change in this PR is based on an assumption that is false, no? The change modified the code so instead of making a run-time check, it performs a compile-time check, based on the assumption that all valid comparisons can be checked at compile-time

so to avoid it, a compile-time error is better

But we are saying that for some valid cases (the one above) the compiler does not know at compile time if a type is a class or not, so the current code does not allow valid comparisons that previously (with the run-time check) were allowed

@acecilia
Copy link
Contributor

I would consider above code snippet correct: it should build and run as expected without further modifications. Wdyt?

@NachoSoto could you please reconsider this. I am considering opening a PR reverting the change here, but would like to hear if you have any alternative for the stated problem before doing so

@NachoSoto
Copy link
Contributor Author

If you prefer to do this at runtime you could always add an overload for Any that casts it to AnyObject.

@acecilia
Copy link
Contributor

If you prefer to do this at runtime

I do not prefer it, but protocol comparisons I believe cannot happen at compile time, as I shown above

you could always add an overload for Any that casts it to AnyObject.

@NachoSoto I cannot imagine how. Can you elaborate?

@NachoSoto
Copy link
Contributor Author

extension Expectation where T == Any {
    public static func === (lhs: Expectation, rhs: Any?) {
        lhs.to(beIdenticalTo(rhs as? AnyObject))
    }
}

@acecilia
Copy link
Contributor

acecilia commented Sep 1, 2022

Thanks for that. But above code does not build, throws the error Cannot convert value of type 'Predicate<AnyObject>' to expected argument type 'Predicate<Any>'

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants