I’d like to discuss how we deal with nullary (def f: Int
) and nilary (def f(): Int
) methods.
The trigger for this discussion is that we want to start enforcing the ()
argument list when calling a nilary method: def f() = 1; f
is allowed in 2.13, deprecated in -Xsource:2.14
and an error in Scala 3.
An exception is made for Java-defined methods, so (new Object).toString
is allowed. This is probably the reason ()
-insertion was added in the first place.
Enforcing this consistency has a number of downstream effects, which I’d like to highlight in this thread.
Overriding
In Scala 2.13, def f
can override def f()
and vice-versa.
There is however a special case (in the spec): when def f
overrides def f()
, the compiler adds the ()
parameter list to the overriding definition. This implies that you cannot, for example, define def toString
without parameter list, the compiler will always convert it to def toString()
.
This causes spurious deprecation warnings with -Xsource:2.14
:
scala> class C { override def toString = "C" }
scala> (new C).toString
^
warning: Auto-application to `()` is deprecated.
res0: String = C
Scala 3 is strict in overriding signatures: def f
cannot override def f()
or vice-versa. An exception is made for Java interop, def f
can override def f()
if the latter is Java-rooted.
Scala 3 doesn’t do the adaptation to insert a ()
parameter list, so C.toString
is nullary. A call-site exception allows calling methods overriding Java-rooted nilary methods with or without ()
, or Java-rooted nullary methods with ()
.
Core Methods
Scala defines Any.##()
, Any.hashCode()
, Any.toString()
and Object.##()
. In Scala 2.13, all of these are nilary, so we get deprecation warnings with -Xsource:2.14
:
scala> 1.toString
^
warning: Auto-application to `()` is deprecated.
scala> (new Object).##
^
warning: Auto-application to `()` is deprecated.
In Scala 3, ##
is defined as nullary, so 1.##()
is an error. Any.hashCode
and Any.toString
seem to benefit from the call-site exception, so they can be invoked with or without ()
.
Standard Library
There are a lot of override def hashCode()
and override def toString()
definitions in the standard library. We would like to change them to be nullary.
Eta-expansion
Eta-expansion is only lightly affected.
-
def f() = 1; f
- In 2.13,
f
is invoked by inserting()
. In 2.14, a deprecation warning is shown. - In Scala 3, this is an error. Scala will not eta-expand in this case.
- In 2.13,
-
def f() = 1
thenf: (() => Int)
eta-expands, no change here -
def f = 1
thenf: (() => Int)
is a type error (found Int, expected () => Int
), no change here -
def f() = 1
anddef f = 1
can both be eta-expanded asf _
, no change here -
def f(x: Int)
thenf
is an error in 2.13, eta-expands in 2.14 and Scala 3
Proposed changes for 2.14
- Deprecation warning when calling
def f()
asf
, except if it’s Java-defined (already done) - Create a
@deprecatedNilary
annotation such that@deprecatedNilary def f = 0
can be invoked asf()
, with a deprecation warning - Change
Any.##()
,Any.hashCode()
,Any.toString()
andObject.##
to be nullary, mark them@deprecatedNilary
- Change
override def hashCode()
andoverride def toString()
to nullary across the standard library, mark them@deprecatedNilary
- Deprecation warning when
def f
overridesdef f()
, except when the overridden method is Java-defined. Deprecation warning whendef f()
overridesdef f
. - Drop the special case for overriding
f()
from the spec
Note that the result would be more strict than what dotty does today. Dotty allows a f
/ f()
mismatch at callsites as long as there exists some Java-defined method overridden by f
, for example
scala> class C { override def toString = "C" }
// defined class C
scala> class D extends C { override def toString() = "D" }
// defined class D
scala> (new C).toString + (new C).toString() + (new D).toString + (new D).toString()
val res0: String = CCDD
I think we should be more strict, i.e.,
- disallow the override in
D
(a Scala-definedtoString
is overridden, so the signature should match) - disallow
(new C).toString()
(it’s Scala-defined nullary) - disallow
(new D).toString
(it’s Scala-defined nilary)