views:

188

answers:

6

This is a troublesome violation of type safety in my project, so I'm looking for a way to disable it. It seems that if a function takes an AnyRef (or a java.lang.Object), you can call the function with any combination of parameters, and Scala will coalesce the parameters into a Tuple object and invoke the function.

In my case the function isn't expecting a Tuple, and fails at runtime. I would expect this situation to be caught at compile time.

object WhyTuple {
 def main(args: Array[String]): Unit = {
  fooIt("foo", "bar")
 }
 def fooIt(o: AnyRef) {
  println(o.toString)
 }
}

Output:

(foo,bar)
+1  A: 

The compile is capable of interpreting methods without round brackets. So it takes the round brackets in the fooIt to mean Tuple. Your call is the same as:

fooIt( ("foo","bar") )

That being said, you can cause the method to exclude the call, and retrieve the value if you use some wrapper like Some(AnyRef) or Tuple1(AnyRef).

Fred Haslam
Unless I'm missing something this isn't an answer so much as a summary of the question's premise.
sepp2k
A: 

I think the definition of (x, y) in Predef is responsible. The "-Yno-predefs" compiler flag might be of some use, assuming you're willing to do the work of manually importing any implicits you otherwise need. By that I mean that you'll have to add import scala.Predef._ all over the place.

sblundy
-1 Theories are great, but why not test them out first?
retronym
+7  A: 

This is actually a quirk of the parser, not of the type system or the compiler. Scala allows zero- or one-arg functions to be invoked without parentheses, but not functions with more than one argument. So as Fred Haslam says, what you've written isn't an invocation with two arguments, it's an invocation with one tuple-valued argument. However, if the method did take two arguments, the invocation would be a two-arg invocation. It seems like the meaning of the code affects how it parses (which is a bit suckful).

As for what you can actually do about this, that's tricky. If the method really did require two arguments, this problem would go away (i.e. if someone then mistakenly tried to call it with one argument or with three, they'd get a compile error as you expect). Don't suppose there's some extra parameter you've been putting off adding to that method? :)

Sam Stokes
FYI, if you add more parameters to the *call* you just end up with higher level tuples: fooIt("foo", "bar", "jam") produces Tuple3. {commenting on the call, not the method signature}
Fred Haslam
Good point, I'll edit to clarify that.
Sam Stokes
+4  A: 

What about something like this:

object Qx2 {
    @deprecated def callingWithATupleProducesAWarning(a: Product) = 2
    def callingWithATupleProducesAWarning(a: Any) = 3
}

Tuples have the Product trait, so any call to callingWithATupleProducesAWarning that passes a tuple will produce a deprecation warning.

James Moore
Cute, but he may legitimately want to pass a value of type `Product` (think any case class) to the method.
retronym
True - I'm being lazy. I think you'll have to add n individual methods for all the TupleN types you want to protect against if you need to pass a Product in normal use.
James Moore
+4  A: 

No implicits or Predef at play here at all -- just good old fashioned compiler magic. You can find it in the type checker. I can't locate it in the spec right now.

If you're motivated enough, you could add a -X option to the compiler prevent this.

Alternatively, you could avoid writing arity-1 methods that accept a supertype of TupleN.

retronym
A: 

Could you also add a two-param override, which would prevent the compiler applying the syntactic sugar? By making the types taking suitably obscure you're unlikely to get false positives. E.g:

object WhyTuple {

  ...

  class DummyType

  def fooIt(a: DummyType, b: DummyType) {
    throw new UnsupportedOperationException("Dummy function - should not be called")
  }
}
pdbartlett
Of course, this isn't very scalable if you have many such methods, or if you need to do it for more than 2 params.
pdbartlett