-
Notifications
You must be signed in to change notification settings - Fork 407
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
Widen "oneOf" argument to "Iterable" from "Seq" #438
Conversation
Seems like an oversight that was never caught. I'm surprised it never came up before. This is a binary compatibility change. It will require an override in MiMa. Something like: --- project/MimaSettings.scala
+++ project/MimaSettings.scala
@@ -12,9 +12,14 @@ object MimaSettings {
removedPrivateMethods.map(exclude[DirectMissingMethodProblem](_)) ++
newMethods.map(exclude[ReversedMissingMethodProblem](_)) ++
removedPrivateClasses.map(exclude[MissingClassProblem](_)) ++
+ changedMethods.map(exclude[IncompatibleMethTypeProblem](_)) ++
otherProblems
)
+ private def changedMethods = Seq(
+ "org.scalacheck.Gen.oneOf"
+ )
+
private def newMethods = Seq(
)
|
Thanks for the patch @ashawley! I have not used MiMa before, so that may have taken me a while to resolve. I squashed the patch into the current commit. Hopefully the build passes now. |
Wow! The previous build seems to have ran into an extremely rare flaky test.
The test samples a number I believe the odds of encountering this error are 1 / 1000 (assuming that 100 samples are generated for each invocation of Looks like we get to submit a new feature and a bug fix in the same PR. :) |
Hi @ashawley and @Philippus , I have addressed the comments in the first round of reviews. Is there anything else we need to do before merging? :) |
LGTM. But @rickynils will have the final word on this. |
This seems fine. But that MiMa change shouldn't be there, since this actually is a binary incompatible change, and we shouldn't merge this until the major version number has been bumped. @ashawley Or am I wrong? |
Yeah, I was thinking it would have to wait for the next major version, but we could probably use function overloading to keep |
Thanks for reviewing the work @rickynils . The consensus appears to be that the original proposed change introduces a binary incompatible change, so we should revert the MiMa change and perhaps consider introducing this work as part of the next major release. I am unfamiliar with the release process for this project, but perhaps I can help expedite the general process. I will proceed as follows.
For 2(a), I believe an independent PR for fixing a flaky test should be straightforward to merge. I will create a new PR for this and tag the relevant current reviewers. For 2(b), I will reuse this PR and isolate the change to relax the method signature. As a question for the maintainers, @rickynils and @ashawley , how should we proceed with merging the proposed change in 2(b) as part of a major release? |
I predict if you just revert the MiMa change and use function overloading to keep both methods it would be fine. For 2(b), you could add a comment that mentions this issue number and says the old method is around for binary compatibility and can be deleted in the next major version. |
Thanks @ashawley . Based on your advice, I have updated the PR as follows.
To continue isolating the two commits for the proposed change and the test fix, the commit history of the PR has been rewritten. |
* @todo Remove this overloaded method in the next major release. See #438. | ||
*/ | ||
def oneOf[T](xs: Seq[T]): Gen[T] = | ||
oneOf(xs.asInstanceOf[Iterable[T]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the cast necessary? I assumed that it would just be oneOf(xs)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, a form of static casting is necessary. Without the cast, the following example would lead to an infinite loop.
val xs: Seq[Int] = ???
Gen.oneOf(xs)
For background, Scala (and Java) dispatches methods dynamically based on the run-time type of the receiver, i.e. this
, and then statically based on the compile-time type of the arguments, e.g. xs
. Hence, a recursive overloaded method with the same arguments (without casting) should always dispatch to itself.
For static casting, I believe that an alternative syntax (unique to Scala) is type ascription, which has the form
oneOf(xs: Iterable[T])
However, I am uncertain of how it would be behave differently than asInstanceOf
; I can look into this later tonight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reading into the subject, it seems that the type ascription will provide a compile-time hint to the compiler to dispatch the appropriate overloaded method. Since this is a minor run-time improvement and likely better Scala style, I will proceed with this unless there is a better option.
Thanks for bringing this to my attention @ashawley . :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only other option would be doing oneOf(xs.toIterable)
. It sounds like the type annotation is best and does the right thing. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at the results of deconstructing the class file with javap
and it seems the type annotation is the equivalent of using asInstanceOf
, and toIterable
gives an unnecessary extra call, so really it's a question of readability between the type annotation and asInstanceOf
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type annotation is the equivalent of using asInstanceOf
Sorry to butt in, I'm afraid that's only true of the final byte code, there is a very important difference at compile time. The type annotation is safer in that it has a compile time check, the asInstanceOf
throws all type safety out the window:
> "" : Int
<console>:8: error: type mismatch;
found : String("")
required: Int
"" : Int
> "".asInstanceOf[Int]
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
As you can see, String
is definitely not Int
, but you can force the conversation anyway with asInstanceOf
and it will quite happily compile (but may fail at runtime).
In terms of the code in question, you should absolutely use a type annotation. asInstanceOf
will continue to work even if xs
later changes to something that's not actually an Iterable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for verifying the assumptions @ashawley and @charleso . Nice to see that we have two empirical tests for the runtime behavior; learning something new about the internals everyday :)
For completeness, the compile-time conversion behavior of type ascription is documented in the Scala 2.11 specification.
The typed expression e:T has type T. The type of expression e is expected to conform to T. The result of the expression is the value of e converted to type T.
This looks good to merge to me. Sorry that I didn't think of avoiding the binary compatibility change earlier. |
Hi maintainers,
For convenience, we would like to use the
oneOf
combinator on sets in our tests. So we have widened the argument toIterable
fromSeq
and added a test. After merging, the following snippet should compile.Background. Currently, the
oneOf
combinator is restricted toSeq
. To use sets, there are currently two workarounds. First, we could convert it to a sequence. Second, we could use theGen.pick[T]
combinator. The workarounds are shown below.However, both of these workarounds are inconvenient if the
oneOf
combinator could be widened to acceptIterable
.Additionally, we noticed that the following combinators also accept
Iterable
, so we believe that this change should make the API more uniform.Gen.someOf[T]
Gen.atLeastOne[T]
As a final note, the
Iterable
trait should be compatible with the Scala 2.13 collections work. :)