scala / scala3

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

Singleton type of varargs #18119

Open prolativ opened 1 year ago

prolativ commented 1 year ago

Compiler version

3.3.2-RC1-bin-20230630-c629090-NIGHTLY and before

Minimized code

The following snippet contains multiple cases obtained by uncommenting one of the commented lines at a time.

object Test {
  def foo(xs: Int*)(ys: xs.type) = ys
  def bar(xs: Int*)(ys: xs.type*) = ys
  val ints = Seq(1)

  // val f1 = foo()()
  // val f2 = foo(1)(1)
  // val f3 = foo(1)(1, 1)
  // val f4 = foo(1, 1)(1, 1)
  // val f5 = foo(1, 2)(1, 2)
  // val f6 = foo(1, 2)(Seq(1, 2): _*)
  // val f7 = foo(ints: _*)(ints)
  // val f8 = foo(ints: _*)(ints: _*)

  // val b1 = bar()()
  // val b2 = bar(1)(1)
  // val b3 = bar(1)(1, 1)
  // val b4 = bar(1, 1)(1, 1)
  // val b5 = bar(1, 2)(1, 2)
  // val b6 = bar(1, 2)(Seq(1, 2): _*)
  // val b7 = bar(ints: _*)(ints)
  // val b8 = bar(ints: _*)(ints: _*)
}

Output

case scala 3.nightly scala 2.13.11
f1 crash error
f2 crash success
f3 crash error
f4 crash error
f5 crash error
f6 error error
f7 crash error
f8 error error
b1 success crash
b2 error success
b3 error success
b4 error crash
b5 error crash
b6 error crash
b7 error error
b8 error error

Stack trace for a crash in 3.3.2-RC1-bin-20230630-c629090-NIGHTLY (taking f2 as example):

```scala Exception in thread "main" java.util.NoSuchElementException: head of empty list at scala.collection.immutable.Nil$.head(List.scala:662) at scala.collection.immutable.Nil$.head(List.scala:661) at dotty.tools.dotc.typer.Applications$Application.matchArgs(Applications.scala:638) at dotty.tools.dotc.typer.Applications$Application.init(Applications.scala:492) at dotty.tools.dotc.typer.Applications$TypedApply.(Applications.scala:777) at dotty.tools.dotc.typer.Applications$ApplyToUntyped.(Applications.scala:894) at dotty.tools.dotc.typer.Applications.ApplyTo(Applications.scala:1124) at dotty.tools.dotc.typer.Applications.ApplyTo$(Applications.scala:352) at dotty.tools.dotc.typer.Typer.ApplyTo(Typer.scala:116) at dotty.tools.dotc.typer.Applications.simpleApply$1(Applications.scala:967) at dotty.tools.dotc.typer.Applications.realApply$1$$anonfun$2(Applications.scala:1050) at dotty.tools.dotc.typer.Typer.tryEither(Typer.scala:3392) at dotty.tools.dotc.typer.Applications.realApply$1(Applications.scala:1061) at dotty.tools.dotc.typer.Applications.typedApply(Applications.scala:1099) at dotty.tools.dotc.typer.Applications.typedApply$(Applications.scala:352) at dotty.tools.dotc.typer.Typer.typedApply(Typer.scala:116) at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3109) at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3176) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3252) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3256) at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3368) at dotty.tools.dotc.typer.Namer.typedAheadExpr$$anonfun$1(Namer.scala:1656) at dotty.tools.dotc.typer.Namer.typedAhead(Namer.scala:1646) at dotty.tools.dotc.typer.Namer.typedAheadExpr(Namer.scala:1656) at dotty.tools.dotc.typer.Namer.typedAheadRhs$1$$anonfun$1(Namer.scala:1915) at dotty.tools.dotc.inlines.PrepareInlineable$.dropInlineIfError(PrepareInlineable.scala:243) at dotty.tools.dotc.typer.Namer.typedAheadRhs$1(Namer.scala:1915) at dotty.tools.dotc.typer.Namer.rhsType$1(Namer.scala:1923) at dotty.tools.dotc.typer.Namer.cookedRhsType$1(Namer.scala:1941) at dotty.tools.dotc.typer.Namer.lhsType$1(Namer.scala:1942) at dotty.tools.dotc.typer.Namer.inferredResultType(Namer.scala:1953) at dotty.tools.dotc.typer.Namer.inferredType$1(Namer.scala:1694) at dotty.tools.dotc.typer.Namer.valOrDefDefSig(Namer.scala:1700) at dotty.tools.dotc.typer.Namer$Completer.typeSig(Namer.scala:787) at dotty.tools.dotc.typer.Namer$Completer.completeInCreationContext(Namer.scala:934) at dotty.tools.dotc.typer.Namer$Completer.complete(Namer.scala:814) at dotty.tools.dotc.core.SymDenotations$SymDenotation.completeFrom(SymDenotations.scala:174) at dotty.tools.dotc.core.Denotations$Denotation.completeInfo$1(Denotations.scala:187) at dotty.tools.dotc.core.Denotations$Denotation.info(Denotations.scala:189) at dotty.tools.dotc.core.SymDenotations$SymDenotation.ensureCompleted(SymDenotations.scala:393) at dotty.tools.dotc.typer.Typer.retrieveSym(Typer.scala:3050) at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:3075) at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3175) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3252) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3256) at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3278) at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3324) at dotty.tools.dotc.typer.Typer.typedClassDef(Typer.scala:2742) at dotty.tools.dotc.typer.Typer.typedTypeOrClassDef$1(Typer.scala:3097) at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:3101) at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3175) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3252) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3256) at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3278) at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3324) at dotty.tools.dotc.typer.Typer.typedPackageDef(Typer.scala:2873) at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3143) at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3176) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3252) at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3256) at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3368) at dotty.tools.dotc.typer.TyperPhase.typeCheck$$anonfun$1(TyperPhase.scala:44) at dotty.tools.dotc.typer.TyperPhase.typeCheck$$anonfun$adapted$1(TyperPhase.scala:50) at scala.Function0.apply$mcV$sp(Function0.scala:42) at dotty.tools.dotc.core.Phases$Phase.monitor(Phases.scala:440) at dotty.tools.dotc.typer.TyperPhase.typeCheck(TyperPhase.scala:50) at dotty.tools.dotc.typer.TyperPhase.runOn$$anonfun$3(TyperPhase.scala:84) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) at scala.collection.immutable.List.foreach(List.scala:333) at dotty.tools.dotc.typer.TyperPhase.runOn(TyperPhase.scala:84) at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:246) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1321) at dotty.tools.dotc.Run.runPhases$1(Run.scala:262) at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:270) at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:279) at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:67) at dotty.tools.dotc.Run.compileUnits(Run.scala:279) at dotty.tools.dotc.Run.compileSources(Run.scala:194) at dotty.tools.dotc.Run.compile(Run.scala:179) at dotty.tools.dotc.Driver.doCompile(Driver.scala:37) at dotty.tools.dotc.Driver.process(Driver.scala:197) at dotty.tools.dotc.Driver.process(Driver.scala:165) at dotty.tools.dotc.Driver.process(Driver.scala:177) at dotty.tools.dotc.Driver.main(Driver.scala:207) at dotty.tools.dotc.Main.main(Main.scala) ```

Expectation

For sure the compiler should not crash but I'm not really sure which of the cases (if any) should compile successfully. The semantics of singleton vararg types in general are quite unclear. E.g. they cannot be used as declared return types. Successfully compiling b3 in scala 2.13 seems to suggest that xs.type is a subtype of Int rather than Int* or Seq[Int]. @odersky should we disallow singleton vararg types at all to prevent such strange corner cases? Alternatively which of the cases should compile?

nicolasstucki commented 1 year ago

The first question we should ask ourselves is if xs is a stable path. If not, those signatures would just be invalid.

nicolasstucki commented 1 year ago

We have a precedent of rejecting path-dependent varargs result types

1 |def test(xs: Int*): xs.type = xs
  |    ^
  |    Cannot return repeated parameter type xs.type

But at the same time we seem to allow

def test(xs: Int*): Option[xs.type] = Some(xs)

Maybe the check is not good enough.

odersky commented 1 year ago

I think it's simplest to disallow xs.type if xs is a vararg parameter.

nicolasstucki commented 1 year ago

We aslo have the following case

def foo(xs: Int*)(ys: xs.type) = ys
val ints = Array(1)
val f7 = foo(ints*)(ints)

In this case inits is wrapped in with wrapIntArray. This implies that ys will not be the same instance as xs.

This seems to imply that a varargs is not guaranteed to be stable. Making them unstable might be the simples solution.

nicolasstucki commented 1 year ago

I think it's simplest to disallow xs.type if xs is a vararg parameter.

Probably anywhere nested in a methodic type. To cover the Option[xs.type] return type as well.

odersky commented 1 year ago

Yes, I'd do it on type formation as a check in typedSingletonTypeTree