scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.86k stars 1.06k forks source link

`infix` keyword is allowed where it has no effect, and has undocumented uses #17738

Open Sporarum opened 1 year ago

Sporarum commented 1 year ago

Compiler version

Tested on 3.3.1-RC1 with -source:future, but unlikely to have changed

Current State

The reference page describing infix calls mentions two places where infix is allowed (without saying these are necessarily the only two):

trait MultiSet:
  infix def union(other: MultiSet): MultiSet

val s1, s2: MultiSet

s1 union s2

and

infix type or[X, Y]
val x: String or Int = ???

However, I found infix is also allowed in the following (doubles as example for later):

object Ext:
  infix object O:
    def apply(x: Char) = 5
  infix val v: (String => String) = s => s
  infix def m(x: Int) = ???
  infix class C(x: Int)
  infix given g(using x: Int): (String => String) = s => s * x

infix def toplevel(x: Any, y: Any) = ???

Notably, infix is not allowed on import and export

toplevel can never be called as infix, the intent was probably an infix extension method

I would have expected to be able to call for example v in an infix matter, this is not allowed, all of the following throw "expression expected but end of statement found" (also the case if we remove the infixs):

val fail1 = Ext O
val fail2 = Ext v
val fail3 = Ext m
val fail4 = Ext C

given Int = 4
val fail5 = Ext g

If we add right-hand sides, we have the following:

val rhs1 = Ext O 'c' //warning: Alphanumeric method O is not declared infix; it should not be used as infix operator.
Instead, use method syntax .O(...) or backticked identifier `O`.
val rhs2 = Ext v "hello"
val rhs3 = Ext m 4
val rhs4 = Ext C 5 //warning: Alphanumeric method C is not declared infix; it should not be used as infix operator.
Instead, use method syntax .C(...) or backticked identifier `C`.

But neither O nor C are methods, and more importantly, they are both inline !

And finally if we remove infix from the definitions, we get this (with shortened warnings):

val ninfix1 = Ext O 'c' //warning: not infix
val ninfix2 = Ext v "hello"
val ninfix3 = Ext m 4 //warning: not infix
val ninfix4 = Ext C 5  //warning: not infix

given Int = 4
val fail6 = Ext g "hello"

As we can see, neither v nor g emit a warning ! (This might be because Function1.apply was compiled with Scala 2)

Adding infix to O.apply leads to no warnings, whether or not O is itself infix!

The reference is also silent on types taking any other amount than 2 parameters, here is what I found:

The following is also undocumented:

object Out:
  class Student(name: String, govtId: Int):
    infix def this(name: String) =
      this(name, 0)

val p = Out Student "James"

Summary

infix is valid, and has undocumented positive effect on traits and classes taking exactly 2 type parameters, and types taking 0 parameters.

infix is valid, but has no effect on val, object, given, as well as in the following particular cases:

Expectation

infix should only be allowed in front of constructs it can modify. Currently: def, type, trait, class In other cases, it should return a similar error as infix export, or a better one

In cases where infix has no effect (currently: "particular cases" above), a warning should be emitted

Every ninfixN should throw a "not infix" warning

The following should display a special warning:

object Ext2:
  infix class C[A, B](x: Int)

val  special = Ext2 C 4 //warning: While C is infix, this only affects the type C and not the constructor

infix auxiliary constructors should be either documented, emit a warning, or throw an error

Sporarum commented 1 year ago

Ping @sjrd @smarter as we discussed this briefly in person

Sporarum commented 1 year ago

For the reference part, it might be useful to tackle the two following issues at the same time: https://github.com/lampepfl/dotty/issues/17593 https://github.com/lampepfl/dotty/issues/17429

Sporarum commented 1 year ago

We discussed with @mbovel, and while this is way too big to be a Spree issue in itself, subparts seem suitable. If smaller issues are opened, they will be mentioned below