Skip to content

Commit 574a148

Browse files
oderskyDarkDimius
authored andcommitted
Added auto-tupling.
Auto-tupling should satisfy the following spec. 1. An application `f(args)` where `f` is a non-overloaded method which has a single, non-repeated parameter as its first parameter list and where args consists of two or more arguments is expanded to `f((args))`. 2. A constructor pattern `C(args)` where `C.unapply` is a non-overloaded method which has a single, non-repeated parameter as its first parameter list and where args consists of two or more arguments is expanded to `C((args))`. Auto-tupling can be disabled by language feature "noAutoTupling". Conflicts: test/dotc/tests.scala
1 parent bff6b09 commit 574a148

File tree

9 files changed

+73
-5
lines changed

9 files changed

+73
-5
lines changed

src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ class Definitions {
215215
lazy val JavaSerializableClass = ctx.requiredClass("java.lang.Serializable")
216216
lazy val ComparableClass = ctx.requiredClass("java.lang.Comparable")
217217
lazy val ProductClass = ctx.requiredClass("scala.Product")
218-
lazy val LanguageModuleClass = ctx.requiredModule("dotty.language").moduleClass
218+
lazy val LanguageModuleClass = ctx.requiredModule("dotty.language").moduleClass.asClass
219219

220220
// Annotation base classes
221221
lazy val AnnotationClass = ctx.requiredClass("scala.annotation.Annotation")

src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
55
import SymDenotations._
66
import config.Printers._
77
import Decorators._
8+
import StdNames._
89
import util.SimpleMap
910

1011
trait TypeOps { this: Context =>
@@ -300,6 +301,10 @@ trait TypeOps { this: Context =>
300301
def hasOption = ctx.base.settings.language.value exists (s => s == featureName || s == "_")
301302
hasImport || hasOption
302303
}
304+
305+
/** Is auto-tupling enabled? */
306+
def canAutoTuple =
307+
!featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling)
303308
}
304309

305310
object TypeOps {

src/dotty/tools/dotc/core/Types.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,13 @@ object Types {
685685
case _ => Nil
686686
}
687687

688+
/** The parameter types in the first parameter section of a PolyType or MethodType, Empty list for others */
689+
final def firstParamTypes: List[Type] = this match {
690+
case mt: MethodType => mt.paramTypes
691+
case pt: PolyType => pt.resultType.firstParamTypes
692+
case _ => Nil
693+
}
694+
688695
/** Is this either not a method at all, or a parameterless method? */
689696
final def isParameterless: Boolean = this match {
690697
case mt: MethodType => false

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,17 @@ trait Applications extends Compatibility { self: Typer =>
434434
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
435435

436436
def realApply(implicit ctx: Context): Tree = track("realApply") {
437-
val proto = new FunProto(tree.args, pt, this)
437+
var proto = new FunProto(tree.args, pt, this)
438438
val fun1 = typedExpr(tree.fun, proto)
439+
440+
// Warning: The following line is dirty and fragile. We record that auto-tupling was demanded as
441+
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application.
442+
// This crucially relies on he fact that `proto` is used only in a single call of `adapt`,
443+
// otherwise we would get possible cross-talk between different `adapt` calls using the same
444+
// prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with
445+
// a modified tree but this would be more convoluted and less efficient.
446+
if (proto.isTupled) proto = proto.tupled
447+
439448
methPart(fun1).tpe match {
440449
case funRef: TermRef =>
441450
tryEither { implicit ctx =>
@@ -676,7 +685,10 @@ trait Applications extends Compatibility { self: Typer =>
676685
var argTypes = unapplyArgs(unapplyApp.tpe)
677686
for (argType <- argTypes) assert(!argType.isInstanceOf[TypeBounds], unapplyApp.tpe.show)
678687
val bunchedArgs = argTypes match {
679-
case argType :: Nil if argType.isRepeatedParam => untpd.SeqLiteral(args) :: Nil
688+
case argType :: Nil =>
689+
if (argType.isRepeatedParam) untpd.SeqLiteral(args) :: Nil
690+
else if (args.lengthCompare(1) > 0 && ctx.canAutoTuple) untpd.Tuple(args) :: Nil
691+
else args
680692
case _ => args
681693
}
682694
if (argTypes.length != bunchedArgs.length) {

src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,20 @@ object ProtoTypes {
187187
typer.adapt(targ, formal)
188188
}
189189

190+
private var myTupled: Type = NoType
191+
192+
/** The same proto-type but with all arguments combined in a single tuple */
193+
def tupled: FunProto = myTupled match {
194+
case pt: FunProto =>
195+
pt
196+
case _ =>
197+
myTupled = new FunProto(untpd.Tuple(args) :: Nil, resultType, typer)
198+
tupled
199+
}
200+
201+
/** Somebody called the `tupled` method of this prototype */
202+
def isTupled: Boolean = myTupled.isInstanceOf[FunProto]
203+
190204
override def toString = s"FunProto(${args mkString ","} => $resultType)"
191205

192206
def map(tm: TypeMap)(implicit ctx: Context): FunProto =

src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,8 +1072,16 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
10721072
}
10731073
}
10741074

1075-
def adaptToArgs(wtp: Type, pt: FunProto) = wtp match {
1076-
case _: MethodType | _: PolyType => tree
1075+
def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
1076+
case _: MethodType | _: PolyType =>
1077+
def isUnary = wtp.firstParamTypes match {
1078+
case ptype :: Nil => !ptype.isRepeatedParam
1079+
case _ => false
1080+
}
1081+
if (pt.args.lengthCompare(1) > 0 && isUnary && ctx.canAutoTuple)
1082+
adaptToArgs(wtp, pt.tupled)
1083+
else
1084+
tree
10771085
case _ => tryInsertApply(tree, pt) {
10781086
val more = tree match {
10791087
case Apply(_, _) => " more"

test/dotc/tests.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class tests extends CompilerTest {
5757
@Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1)
5858
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4)
5959
@Test def neg_companions = compileFile(negDir, "companions", xerrors = 1)
60+
@Test def neg_autoTupling = compileFile(posDir, "autoTuplingTest", "-language:noAutoTupling" :: Nil, xerrors = 3)
61+
@Test def neg_autoTupling2 = compileFile(negDir, "autoTuplingTest", xerrors = 3)
6062

6163
@Test def dotc = compileDir(dotcDir + "tools/dotc", twice)
6264
@Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice)

tests/neg/autoTuplingTest.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import dotty.language.noAutoTupling
2+
3+
object autoTuplingNeg {
4+
5+
val x = Some(1, 2)
6+
7+
x match {
8+
case Some(a, b) => a + b
9+
case None =>
10+
}
11+
}

tests/pos/autoTuplingTest.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object autoTupling {
2+
3+
val x = Some(1, 2)
4+
5+
x match {
6+
case Some(a, b) => a + b
7+
case None =>
8+
}
9+
}

0 commit comments

Comments
 (0)