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

A single structural type cannot match both kinds of zero-arg methods #4506

Closed
scabug opened this issue Apr 22, 2011 · 11 comments
Closed

A single structural type cannot match both kinds of zero-arg methods #4506

scabug opened this issue Apr 22, 2011 · 11 comments

Comments

@scabug
Copy link

scabug commented Apr 22, 2011

=== What steps will reproduce the problem (please be specific and use wikiformatting)? ===

  type Gettable[T] = { def get: T }
  type GettableParens[T] = { def get(): T }

  def doGet[T](g: NoParens[T]) = g.get
  def doGetParens[T](g: WithParens[T]) = g.get()

  trait NoParens[T] { def get: T }
  trait WithParens[T] { def get(): T }

  def test[T](np: NoParens[T], wp: WithParens[T]) {
    doGet(np)
    doGetParens(np) // compile error (type mismatch)

    doGet(wp) // compile error (type mismatch) [*]
    doGetParens(wp)
  }

=== What is the expected behavior? ===

The line marked with [*] should compile and work. This matches a trait/class containing {def get(): T} with a structural type declared as {def get: T}.

The reason I think this is the appropriate solution has to do with source code calling conventions. Since methods declared without parens can only be called without them, but both forms are callable without parens, then the structural type forcing no-paren usage should match both trait/class forms.

=== What do you see instead? ===

Compile errors as noted above, requiring two separate structural types to deal with this issue.

=== Additional information ===

See issue #2810 for a symptom of this problem, which was "fixed" by changing the method signature in only one library class.

=== What versions of the following are you using? ===

  • Scala: 2.8.1 and 2.9.0rc1
  • Java: Sun/Oracle 1.6.0_24
  • Operating system: Linux (Ubuntu 10.04.2)
@scabug
Copy link
Author

scabug commented Apr 22, 2011

Imported From: https://issues.scala-lang.org/browse/SI-4506?orig=1
Reporter: Todd Vierling (tvierling)

@scabug
Copy link
Author

scabug commented Apr 22, 2011

Todd Vierling (tvierling) said:
I forgot to mention... I haven't looked to see if this requires a change to SLS, but based on the bytecode, it's only a compiler-enforced restriction at the operational level. The two cases operate identically otherwise.

@scabug
Copy link
Author

scabug commented Apr 27, 2011

@magarciaEPFL said:
There are no "two kinds of zero-args methods". There are methods with no value-params-lists and there are also methods with an empty value-param-list. And both are here to stay :-)

Details at http://lampsvn.epfl.ch/trac/scala/changeset/24029

@scabug
Copy link
Author

scabug commented Apr 27, 2011

@paulp said:
I think this bug is valid. To be very relaxed about whether there is an empty parameter list at all the calling sites but very strict about it when defining structural types means we are perpetually being confounded by the otherwise meaningless presence of a pair of parentheses. I think we should either be stricter at the call sites (and there was some momentum for that a while ago, but I think martin decided against) or more liberal here.

Maybe if we had union types and could say

def fn(x: { def f: Int } <or> { def f(): Int}): Int = x.f

then we could at least work around it, even if that is pretty ugly. But as it stands even that outlet is unavailable.

@scabug
Copy link
Author

scabug commented Apr 27, 2011

Todd Vierling (tvierling) said:
Discussion has been opened on scala-language as well:

https://groups.google.com/d/topic/scala-language/0NLRyix9HjA/discussion

@scabug
Copy link
Author

scabug commented Apr 27, 2011

@harrah said:
I agree with the original invalid resolution, since the behavior is as specified for the conformance relation (3.5.2). There is a special case for allowing a parameterless method to override a method with an empty parameter list, but this is just for overriding and not conformance.

Related, I don't see what in the spec allows you to do the opposite and override a method with an empty parameter list with a parameterless method, but the compiler doesn't complain:

scala> class A { def x: Int = 3 }
defined class A

scala> class B extends A { override def x(): Int = 4 }
defined class B

@scabug
Copy link
Author

scabug commented Apr 27, 2011

@harrah said:
I meant "...do the opposite and override a parameterless method with a method with an empty parameter list..."

@scabug
Copy link
Author

scabug commented Apr 27, 2011

@odersky said:
I side with Miguel here. Confusing parameterless methods with nullary methods is generally bad. We have to do it for Java methods, because otherwise we'd always have to write toString(). We then also should do it for Scala methods, because otherwise migrating a Java class to Scala could break arbitrary clients. But there's not reason to generalize this. It's a kludge, not a virtue!

@scabug
Copy link
Author

scabug commented Apr 27, 2011

@paulp said:
I think you guys should have to try implementing some structural types in the real world... we have to do it for java methods AND scala methods, but stop there? Other than this, what's left? I'll concede, but I find it vanishingly unlikely that anyone implementing a structural type would rather see this distinction faithfully preserved than not -- especially once they find it's impossible to cover both methods.

@scabug
Copy link
Author

scabug commented Apr 28, 2011

@Ichoran said:
I will note that this bug is the main reason I avoid using structural types like the plague. It is simply too hard to enforce consistency between using () and not using () in method definitions. Relying upon interfaces like Closeable is way more reliable (i.e. it actually works, instead of actually not working most of the time). (The second reason I avoid them is performance, but when, say, closing a resource that isn't really an issue. Except some people chose close and some chose close().)

Observe:

trait T { def x(): Int }
class C extends T { def x = 5 }   // Works
trait U { def y: Int }
class D extends U { def y() = 5 } // Works

But I'm supposed to keep these parens-or-not straight just so structural typing will work? Not likely!

@scabug scabug closed this as completed May 18, 2011
@scabug
Copy link
Author

scabug commented Jun 29, 2011

Commit Message Bot (anonymous) said:
(extempore in r25172) Warning! Warning! Yes, that's what's in this commit.
Why are you panicking?

Mostly new command line options:

-Xlint // basically, the ones which aren't noisy
-Ywarn-all
-Ywarn-dead-code
-Ywarn-inaccessible // try this one on the library: it makes some good points
-Ywarn-nullary-override
-Ywarn-nullary-unit
-Ywarn-numeric-widen
-Ywarn-value-discard

Some accumulated motivations:

The wontfix resolution of ticket #4506 indicates that "def foo" and "def
foo()" are always going to be treated differently in some situations
and the same in others without users having any way to fix it. Summary
expressed in latest comment with which I agree (and quite sadly, given
that I've done a lot of work to try to make them usable) is "avoid using
structural types like the plague." But the least we can do is warn if
you're using parentheses "wrong".

I think it would be better if the warning about "def foo()" overriding
"def foo" were an error instead. If we have to live with this...

trait Me { def f(): Int }
class A { def f: Int = 5 }
class C extends A with Me { }

// error: Int does not take parameters
def f(x: C) = x.f()

// compiles
def f(x: Me) = x.f()

// error: Int does not take parameters. Mmph, how can a method
// be legal with parameter "Foo" and illegal with parameter
// "Foo with Bar" ?
def f(x: Me with C) = x.f()

The warning about a method contains a reference to a type which is less
accessible than the method itself is obviously to those who recall it
a response to GenTraversable being private and appearing in flatMap's
signature during the 2.9.0 RCs. It avoids warning in the case where the
unnormalized type is inaccessible but the normalized version would be,
but it could use further refinement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant