From f5a8b544e1b1e9034ddc93368dee0d9cde1767a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Apr 2018 13:00:58 +0200 Subject: [PATCH 1/7] Restrict auto-tupling more This was initially an attempt to drop auto-tupling altogether. But it became quickly annoying. The main problem is that it's unnatural to add another pair of parentheses to infix operations. Compare: (x, y) == z z == ((x, y)) // !yuck Same thing for cons: ((1, x)) :: ((2, y)) :: xs // double yuck! So at the very least we have to allow auto-tupling for arguments of infix operators. I went a little bit further and also allowed auto-tupling if the expected type is already a (some kind of) product or tuple of the right arity. The result is this commit. The language import `noAutoTupling` has been removed. The previous auto-tupling is still reported under Scala 2 mode, and there is -rewrite support to add the missing (...) automatically. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 2 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +- .../dotty/tools/dotc/core/Definitions.scala | 19 +++++-- .../src/dotty/tools/dotc/core/NameOps.scala | 11 ++++ .../src/dotty/tools/dotc/core/TypeOps.scala | 9 +-- .../dotty/tools/dotc/typer/Applications.scala | 10 ++-- .../src/dotty/tools/dotc/typer/Typer.scala | 57 ++++++++++++++----- .../dotty/tools/dotc/CompilationTests.scala | 1 - library/src/scalaShadowing/language.scala | 4 -- tests/neg-custom-args/autoTuplingTest.scala | 9 --- tests/neg/autoTuplingTest.scala | 4 +- tests/neg/i1286.scala | 1 - tests/neg/t6920.scala | 2 +- .../CollectionStrawMan6.scala | 2 +- tests/pos/autoTuplingTest.scala | 9 --- tests/pos/i1318.scala | 4 +- tests/pos/i903.scala | 2 +- tests/pos/t1133.scala | 6 +- tests/pos/t2913.scala | 24 -------- .../run/colltest6/CollectionStrawMan6_1.scala | 2 +- tests/run/unapply.scala | 4 +- tests/run/virtpatmat_unapply.scala | 2 +- 23 files changed, 94 insertions(+), 96 deletions(-) delete mode 100644 tests/neg-custom-args/autoTuplingTest.scala delete mode 100644 tests/pos/autoTuplingTest.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index ef8226229a7e..30b4ef2bd284 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1216,7 +1216,7 @@ object desugar { */ private object IdPattern { def unapply(tree: Tree)(implicit ctx: Context): Option[VarInfo] = tree match { - case id: Ident => Some(id, TypeTree()) + case id: Ident => Some((id, TypeTree())) case Typed(id: Ident, tpt) => Some((id, tpt)) case _ => None } diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index d1e91799f31a..c50d154f2827 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -537,7 +537,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => object closure { def unapply(tree: Tree): Option[(List[Tree], Tree, Tree)] = tree match { case Block(_, expr) => unapply(expr) - case Closure(env, meth, tpt) => Some(env, meth, tpt) + case Closure(env, meth, tpt) => Some((env, meth, tpt)) case Typed(expr, _) => unapply(expr) case _ => None } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a3be000dbbc2..3fbb091bb702 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1009,8 +1009,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** An extractor that pulls out type arguments */ object MaybePoly { def unapply(tree: Tree): Option[(Tree, List[Tree])] = tree match { - case TypeApply(tree, targs) => Some(tree, targs) - case _ => Some(tree, Nil) + case TypeApply(tree, targs) => Some((tree, targs)) + case _ => Some((tree, Nil)) } } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 95c5aa94dd37..7260aa1b34a8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -436,7 +436,6 @@ class Definitions { lazy val ArrayModuleType = ctx.requiredModuleRef("scala.Array") def ArrayModule(implicit ctx: Context) = ArrayModuleType.symbol.moduleClass.asClass - lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void) def UnitClass(implicit ctx: Context) = UnitType.symbol.asClass def UnitModuleClass(implicit ctx: Context) = UnitType.symbol.asClass.linkedClass @@ -776,7 +775,7 @@ class Definitions { if (isFunctionClass(tsym)) { val targs = ft.dealias.argInfos if (targs.isEmpty) None - else Some(targs.init, targs.last, tsym.name.isImplicitFunction, tsym.name.isErasedFunction) + else Some((targs.init, targs.last, tsym.name.isImplicitFunction, tsym.name.isErasedFunction)) } else None } @@ -821,8 +820,8 @@ class Definitions { case ArrayOf(elemtp) => def recur(elemtp: Type): Option[(Type, Int)] = elemtp.dealias match { case TypeBounds(lo, hi) => recur(hi) - case MultiArrayOf(finalElemTp, n) => Some(finalElemTp, n + 1) - case _ => Some(elemtp, 1) + case MultiArrayOf(finalElemTp, n) => Some((finalElemTp, n + 1)) + case _ => Some((elemtp, 1)) } recur(elemtp) case _ => @@ -879,6 +878,18 @@ class Definitions { name.length > prefix.length && name.drop(prefix.length).forall(_.isDigit)) + def arity(cls: Symbol, prefix: String): Int = + scalaClassName(cls).applySimple(-1) { name => + if (name.startsWith(prefix)) { + val digits = name.drop(prefix.length) + if (!digits.isEmpty && digits.forall(_.isDigit)) + try digits.toString.toInt + catch { case ex: NumberFormatException => -1 } + else -1 + } + else -1 + } + def isBottomClass(cls: Symbol) = cls == NothingClass || cls == NullClass def isBottomType(tp: Type) = diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 584139248959..6da58c4241e1 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -58,6 +58,12 @@ object NameOps { case _ => false } + def applySimple[T](default: T)(f: SimpleName => T): T = name match { + case name: SimpleName => f(name) + case name: TypeName => name.toTermName.applySimple(default)(f) + case _ => default + } + def likeSpaced(n: PreName): N = (if (name.isTermName) n.toTermName else n.toTypeName).asInstanceOf[N] @@ -81,6 +87,11 @@ object NameOps { } } + def isOpName = name match { + case name: SimpleName => NameTransformer.encode(name) != name + case _ => false + } + def isOpAssignmentName: Boolean = name match { case raw.NE | raw.LE | raw.GE | EMPTY => false diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 498660c9d82b..0ae384c500bc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -17,7 +17,7 @@ import util.Property import collection.mutable import ast.tpd._ import reporting.trace -import reporting.diagnostic.Message +import reporting.diagnostic.{Message, NoExplanation} trait TypeOps { this: Context => // TODO: Make standalone object. @@ -313,10 +313,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. hasImport(ctx.withPhase(ctx.typerPhase)) || hasOption } - /** Is auto-tupling enabled? */ - def canAutoTuple = - !featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling) - def scala2Mode = featureEnabled(defn.LanguageModuleClass, nme.Scala2) @@ -325,7 +321,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. def testScala2Mode(msg: => Message, pos: Position, rewrite: => Unit = ()) = { if (scala2Mode) { - migrationWarning(msg, pos) + migrationWarning( + new NoExplanation(msg.msg ++ "\nThis can be fixed automatically using -rewrite"), pos) rewrite } scala2Mode diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 7d77920e8741..1ebe761671e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -980,10 +980,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val (regularArgs, varArgs) = args.splitAt(argTypes.length - 1) regularArgs :+ untpd.SeqLiteral(varArgs, untpd.TypeTree()).withPos(tree.pos) } - else if (argTypes.lengthCompare(1) == 0 && args.lengthCompare(1) > 0 && ctx.canAutoTuple) - untpd.Tuple(args) :: Nil - else - args + else argTypes match { + case argType :: Nil if args.lengthCompare(1) > 0 && supportsAutoTupling(argType, args) => + untpd.Tuple(args) :: Nil + case _ => + args + } if (argTypes.length != bunchedArgs.length) { ctx.error(UnapplyInvalidNumberOfArguments(qual, argTypes), tree.pos) argTypes = argTypes.take(args.length) ++ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 431721dc44e6..107f382a3930 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2032,6 +2032,45 @@ class Typer extends Namer } } + def supportsAutoTupling(tp: Type, args: List[untpd.Tree])(implicit ctx: Context): Boolean = { + def test(tp: Type): Boolean = tp match { + case tp: TypeRef => + val sym = tp.symbol + defn.isTupleClass(sym) && defn.arity(sym, str.Tuple) == args.length || + defn.isProductClass(sym) && defn.arity(sym, str.Product) == args.length || + !sym.isClass && test(tp.underlying) + case tp: TypeParamRef => + val bounds = ctx.typeComparer.bounds(tp) + test(bounds.lo) || test(bounds.hi) + case tp: TypeProxy => + test(tp.underlying) + case _ => + false + } + test(tp) || { + val pos = Position(args.map(_.pos.start).min, args.map(_.pos.end).max) + ctx.testScala2Mode( + i"""auto-tupling is no longer supported in this case, + |arguments now need to be enclosed in parentheses (...).""", + pos, { patch(pos.startPos, "("); patch(pos.endPos, ")") }) + } + } + + def takesAutoTupledArgs(tp: Type, sym: Symbol, args: List[untpd.Tree])(implicit ctx: Context): Boolean = tp match { + case tp: MethodicType => + tp.firstParamTypes match { + case ptype :: Nil => + !ptype.isRepeatedParam && { + sym.name.isOpName || supportsAutoTupling(ptype, args) + } + case _ => false + } + case tp: TermRef => + tp.denot.alternatives.forall(alt => takesAutoTupledArgs(alt.info, alt.symbol, args)) + case _ => + false + } + /** Perform the following adaptations of expression, pattern or type `tree` wrt to * given prototype `pt`: * (1) Resolve overloading @@ -2119,21 +2158,9 @@ class Typer extends Namer } } - def isUnary(tp: Type): Boolean = tp match { - case tp: MethodicType => - tp.firstParamTypes match { - case ptype :: Nil => !ptype.isRepeatedParam - case _ => false - } - case tp: TermRef => - tp.denot.alternatives.forall(alt => isUnary(alt.info)) - case _ => - false - } - def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { case _: MethodOrPoly => - if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) + if (pt.args.lengthCompare(1) > 0 && takesAutoTupledArgs(wtp, tree.symbol, pt.args)) adapt(tree, pt.tupled, locked) else tree @@ -2499,10 +2526,10 @@ class Typer extends Namer case tp: FlexType => ensureReported(tp) tree - case ref: TermRef => + case ref: TermRef => // this case can happen in case tree.tpe is overloaded pt match { case pt: FunProto - if pt.args.lengthCompare(1) > 0 && isUnary(ref) && ctx.canAutoTuple => + if pt.args.lengthCompare(1) > 0 && takesAutoTupledArgs(ref, ref.symbol, pt.args) => adapt(tree, pt.tupled, locked) case _ => adaptOverloaded(ref) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index d91b789b4f86..c0180adfcd28 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -180,7 +180,6 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/neg-custom-args/allow-double-bindings", allowDoubleBindings) + compileFile("tests/neg-custom-args/i3246.scala", scala2Mode) + compileFile("tests/neg-custom-args/overrideClass.scala", scala2Mode) + - compileFile("tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")) + compileFile("tests/neg-custom-args/i1050.scala", defaultOptions.and("-strict")) + compileFile("tests/neg-custom-args/nopredef.scala", defaultOptions.and("-Yno-predef")) + compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")) + diff --git a/library/src/scalaShadowing/language.scala b/library/src/scalaShadowing/language.scala index 50d2bf069c04..d1d26f77af31 100644 --- a/library/src/scalaShadowing/language.scala +++ b/library/src/scalaShadowing/language.scala @@ -29,7 +29,6 @@ package scalaShadowing * and, for dotty: * * - [[Scala2 `Scala2`] backwards compatibility mode for Scala2 - * - [[noAtoTupling `noAutoTupling`]] disable auto-tupling * * @groupname production Language Features * @groupname experimental Experimental Language Features @@ -193,9 +192,6 @@ object language { /** Where imported, a backwards compatibility mode for Scala2 is enabled */ object Scala2 - /** Where imported, auto-tupling is disabled */ - object noAutoTupling - /* Where imported loose equality using eqAny is disabled */ object strictEquality } diff --git a/tests/neg-custom-args/autoTuplingTest.scala b/tests/neg-custom-args/autoTuplingTest.scala deleted file mode 100644 index 7321a83827bb..000000000000 --- a/tests/neg-custom-args/autoTuplingTest.scala +++ /dev/null @@ -1,9 +0,0 @@ -object autoTupling { - - val x = Some(1, 2) // error when running with -language:noAutoTupling - - x match { - case Some(a, b) => a + b // error // error when running with -language:noAutoTupling - case None => - } -} diff --git a/tests/neg/autoTuplingTest.scala b/tests/neg/autoTuplingTest.scala index 92126ab5dd60..c53ff99fc571 100644 --- a/tests/neg/autoTuplingTest.scala +++ b/tests/neg/autoTuplingTest.scala @@ -1,11 +1,9 @@ -import language.noAutoTupling - object autoTuplingNeg2 { val x = Some(1, 2) // error: too many arguments for method apply: (x: A)Some[A] x match { - case Some(a, b) => a + b // error: wrong number of argument patterns for Some // error: not found: b + case Some(a, b) => a + b // error: wrong number of argument patterns for Some // error: not found: a case None => } } diff --git a/tests/neg/i1286.scala b/tests/neg/i1286.scala index 40db9ab1d46d..b2b74c41a63e 100644 --- a/tests/neg/i1286.scala +++ b/tests/neg/i1286.scala @@ -10,7 +10,6 @@ import scala.io.{ Idontexist4 => Foo } // error import scala.io.{ Idontexist5 => _ } // error import scala.language.dynamics -import scala.language.noAutoTupling import scala.language.idontexist // error object Test diff --git a/tests/neg/t6920.scala b/tests/neg/t6920.scala index 9601ed8d27e6..4a135f0d2d10 100644 --- a/tests/neg/t6920.scala +++ b/tests/neg/t6920.scala @@ -6,5 +6,5 @@ class DynTest extends Dynamic { class CompilerError { val test = new DynTest - test.crushTheCompiler(a = 1, b = 2) // error + test.crushTheCompiler(a = 1, b = 2) // error // error } diff --git a/tests/pos-special/strawman-collections/CollectionStrawMan6.scala b/tests/pos-special/strawman-collections/CollectionStrawMan6.scala index a2ff42ba783e..6c2a37adfda9 100644 --- a/tests/pos-special/strawman-collections/CollectionStrawMan6.scala +++ b/tests/pos-special/strawman-collections/CollectionStrawMan6.scala @@ -651,7 +651,7 @@ object CollectionStrawMan6 extends LowPriority { } def fromIterator[B](it: Iterator[B]): LazyList[B] = - new LazyList(if (it.hasNext) Some(it.next(), fromIterator(it)) else None) + new LazyList(if (it.hasNext) Some((it.next(), fromIterator(it))) else None) } // ------------------ Decorators to add collection ops to existing types ----------------------- diff --git a/tests/pos/autoTuplingTest.scala b/tests/pos/autoTuplingTest.scala deleted file mode 100644 index 523411a1a410..000000000000 --- a/tests/pos/autoTuplingTest.scala +++ /dev/null @@ -1,9 +0,0 @@ -object autoTupling { - - val x = Some(1, 2) - - x match { - case Some(a, b) => a + b - case None => - } -} diff --git a/tests/pos/i1318.scala b/tests/pos/i1318.scala index dfb882825166..6fb6357cbe88 100644 --- a/tests/pos/i1318.scala +++ b/tests/pos/i1318.scala @@ -3,7 +3,7 @@ object Foo { case class T(i: Int) extends S(i) object T { - def unapply(s: S): Option[(Int, Int)] = Some(5, 6) + def unapply(s: S): Option[(Int, Int)] = Some((5, 6)) // def unapply(o: Object): Option[(Int, Int, Int)] = Some(5, 6, 7) } @@ -22,7 +22,7 @@ object Bar { class S(i: Int) extends T(i) object T { - def unapply(s: S): Option[(Int, Int)] = Some(5, 6) + def unapply(s: S): Option[(Int, Int)] = Some((5, 6)) // def unapply(o: Object): Option[(Int, Int, Int)] = Some(5, 6, 7) } diff --git a/tests/pos/i903.scala b/tests/pos/i903.scala index 5afb6e53010c..dc64d6d5fef3 100644 --- a/tests/pos/i903.scala +++ b/tests/pos/i903.scala @@ -17,7 +17,7 @@ object Test { } def test2 = { - val f = "".contains("", (_: Int)) // dotc: + val f = (x: Int) => "".contains(("", x)) // dotc: f.apply(0) // sandbox/eta.scala:18: error: apply is not a member of Boolean(f) // f.apply(0) diff --git a/tests/pos/t1133.scala b/tests/pos/t1133.scala index 2e4793e998f3..60c1751ad402 100644 --- a/tests/pos/t1133.scala +++ b/tests/pos/t1133.scala @@ -11,21 +11,21 @@ object Match object Extractor1 { def unapply(x: Any) = x match { - case x: String => Some(x, x + x, x + x+x, x+x, x) + case x: String => Some((x, x + x, x + x+x, x+x, x)) case _ => None } } object Extractor2 { def unapply(x: Any) = x match { - case x: String => Some(x, x + x, x + x+x) + case x: String => Some((x, x + x, x + x+x)) case _ => None } } object Extractor3 { def unapply(x: Any) = x match { - case x: String => Some(x, x, x) + case x: String => Some((x, x, x)) case _ => None } } diff --git a/tests/pos/t2913.scala b/tests/pos/t2913.scala index f91ed7b51318..b599ca6a2924 100644 --- a/tests/pos/t2913.scala +++ b/tests/pos/t2913.scala @@ -10,8 +10,6 @@ class RichA { } object TestNoAutoTupling { - import language.noAutoTupling // try with on and off - implicit def AToRichA(a: A): RichA = new RichA val a = new A @@ -52,25 +50,3 @@ object Main { () } } - -object TestWithAutoTupling { - - implicit def AToRichA(a: A): RichA = new RichA - - val a = new A - a.foo() - a.foo(1) - - a.foo("") // Without implicits, a type error regarding invalid argument types is generated at `""`. This is - // the same position as an argument, so the 'second try' typing with an Implicit View is tried, - // and AToRichA(a).foo("") is found. - // - // My reading of the spec "7.3 Views" is that `a.foo` denotes a member of `a`, so the view should - // not be triggered. - // - // But perhaps the implementation was changed to solve See https://lampsvn.epfl.ch/trac/scala/ticket/1756 - - a.foo("a", "b") // Without implicits, a type error regarding invalid arity is generated at `foo("", "")`. - // Typers#tryTypedApply:3274 only checks if the error is as the same position as `foo`, `"a"`, or `"b"`. -} - diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 4fad57154775..a380deb9d74c 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -652,7 +652,7 @@ object CollectionStrawMan6 extends LowPriority { } def fromIterator[B](it: Iterator[B]): LazyList[B] = - new LazyList(if (it.hasNext) Some(it.next(), fromIterator(it)) else None) + new LazyList(if (it.hasNext) Some((it.next(), fromIterator(it))) else None) } // ------------------ Decorators to add collection ops to existing types ----------------------- diff --git a/tests/run/unapply.scala b/tests/run/unapply.scala index 7b10030ba76d..db8823f00eef 100644 --- a/tests/run/unapply.scala +++ b/tests/run/unapply.scala @@ -33,7 +33,7 @@ object VarFoo { object Foo { def unapply(x: Any): Option[Product2[Int, String]] = x match { - case y: Bar => Some(y.size, y.name) + case y: Bar => Some((y.size, y.name)) case _ => None } def doMatch1(b:Bar) = b match { @@ -69,7 +69,7 @@ object Foo { object Mas { object Gaz { def unapply(x: Any): Option[Product2[Int, String]] = x match { - case y: Baz => Some(y.size, y.name) + case y: Baz => Some((y.size, y.name)) case _ => None } } diff --git a/tests/run/virtpatmat_unapply.scala b/tests/run/virtpatmat_unapply.scala index 9915b8d924e5..c52556ae0b69 100644 --- a/tests/run/virtpatmat_unapply.scala +++ b/tests/run/virtpatmat_unapply.scala @@ -1,7 +1,7 @@ class IntList(val hd: Int, val tl: IntList) object NilIL extends IntList(0, null) object IntList { - def unapply(il: IntList): Option[(Int, IntList)] = if(il eq NilIL) None else Some(il.hd, il.tl) + def unapply(il: IntList): Option[(Int, IntList)] = if(il eq NilIL) None else Some((il.hd, il.tl)) def apply(x: Int, xs: IntList) = new IntList(x, xs) } From eda9b37a8e9627f2abb498ef6c3d1b39c2e07e6d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Apr 2018 13:48:04 +0200 Subject: [PATCH 2/7] Don't auto-tuple if expected type is a tuple --- .../reporting/UniqueMessagePositions.scala | 2 +- .../dotty/tools/dotc/reporting/trace.scala | 16 ++++----- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 33 ++++--------------- tests/run/t8610.scala | 2 +- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index 6fd971c2a4c0..e63f685632ea 100644 --- a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -21,7 +21,7 @@ trait UniqueMessagePositions extends Reporter { m.pos.exists && { var shouldHide = false for (pos <- m.pos.start to m.pos.end) { - positions get (ctx.source, pos) match { + positions.get((ctx.source, pos)) match { case Some(level) if level >= m.level => shouldHide = true case _ => positions((ctx.source, pos)) = m.level } diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index d42008500bbf..6e34c6f1188d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -9,40 +9,40 @@ import core.Mode object trace { - @inline + @`inline` def onDebug[TD](question: => String)(op: => TD)(implicit ctx: Context): TD = conditionally(ctx.settings.YdebugTrace.value, question, false)(op) - @inline + @`inline` def conditionally[TC](cond: Boolean, question: => String, show: Boolean)(op: => TC)(implicit ctx: Context): TC = { def op1 = op if (Config.tracingEnabled && cond) apply[TC](question, Printers.default, show)(op1) else op1 } - @inline + @`inline` def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T = { def op1 = op if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 else doTrace[T](question, printer, showOp)(op1) } - @inline + @`inline` def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { def op1 = op if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1) } - @inline + @`inline` def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = apply[T](question, printer, false)(op) - @inline + @`inline` def apply[T](question: => String, show: Boolean)(op: => T)(implicit ctx: Context): T = apply[T](question, Printers.default, show)(op) - @inline + @`inline` def apply[T](question: => String)(op: => T)(implicit ctx: Context): T = apply[T](question, Printers.default, false)(op) @@ -59,7 +59,7 @@ object trace { (op: => T)(implicit ctx: Context): T = { // Avoid evaluating question multiple time, since each evaluation // may cause some extra logging output. - lazy val q: String = question + @volatile lazy val q: String = question apply[T](s"==> $q?", (res: Any) => s"<== $q = ${showOp(res)}")(op) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1ebe761671e4..bc7bb90cc70d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -981,7 +981,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => regularArgs :+ untpd.SeqLiteral(varArgs, untpd.TypeTree()).withPos(tree.pos) } else argTypes match { - case argType :: Nil if args.lengthCompare(1) > 0 && supportsAutoTupling(argType, args) => + case argType :: Nil if args.lengthCompare(1) > 0 && canAutoTuple(args) => untpd.Tuple(args) :: Nil case _ => args diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 107f382a3930..c667238ff0ac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2032,37 +2032,18 @@ class Typer extends Namer } } - def supportsAutoTupling(tp: Type, args: List[untpd.Tree])(implicit ctx: Context): Boolean = { - def test(tp: Type): Boolean = tp match { - case tp: TypeRef => - val sym = tp.symbol - defn.isTupleClass(sym) && defn.arity(sym, str.Tuple) == args.length || - defn.isProductClass(sym) && defn.arity(sym, str.Product) == args.length || - !sym.isClass && test(tp.underlying) - case tp: TypeParamRef => - val bounds = ctx.typeComparer.bounds(tp) - test(bounds.lo) || test(bounds.hi) - case tp: TypeProxy => - test(tp.underlying) - case _ => - false - } - test(tp) || { - val pos = Position(args.map(_.pos.start).min, args.map(_.pos.end).max) - ctx.testScala2Mode( - i"""auto-tupling is no longer supported in this case, - |arguments now need to be enclosed in parentheses (...).""", - pos, { patch(pos.startPos, "("); patch(pos.endPos, ")") }) - } + def canAutoTuple(args: List[untpd.Tree])(implicit ctx: Context): Boolean = { + val pos = Position(args.map(_.pos.start).min, args.map(_.pos.end).max) + ctx.testScala2Mode( + i"""auto-tupling is no longer supported in this case, + |arguments now need to be enclosed in parentheses (...).""", + pos, { patch(pos.startPos, "("); patch(pos.endPos, ")") }) } def takesAutoTupledArgs(tp: Type, sym: Symbol, args: List[untpd.Tree])(implicit ctx: Context): Boolean = tp match { case tp: MethodicType => tp.firstParamTypes match { - case ptype :: Nil => - !ptype.isRepeatedParam && { - sym.name.isOpName || supportsAutoTupling(ptype, args) - } + case ptype :: Nil => !ptype.isRepeatedParam && (sym.name.isOpName || canAutoTuple(args)) case _ => false } case tp: TermRef => diff --git a/tests/run/t8610.scala b/tests/run/t8610.scala index 978900d42d09..f0ffabd0db54 100644 --- a/tests/run/t8610.scala +++ b/tests/run/t8610.scala @@ -3,7 +3,7 @@ case class X(name: String) { def x = "Hi, $name" // missing interp def f(p: (Int, Int)): Int = p._1 * p._2 - def g = f(3, 4) // adapted + def g = f((3, 4)) // adapted def u: Unit = () // unitarian universalist } From 38a15134d15224d5211f2ae19f05f92d33c5a15d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 15 Apr 2018 11:45:35 +0200 Subject: [PATCH 3/7] Avoid infix operations with more than one argument on the right. The idiom x op (y, z) will in the future be interpreted as taking a tuple `(y, z)`. So all infix operations with more than one parameter have to be rewritten to method calls. This was for the most part done using an automatic rewrite (introduced in one of the next commits). Most of the tests were not re-formatted afterwards so one sees the traces of the rewrite. E.g. the reqrite would yield x .op (y, z) instead of the more idiomatic x.op(y. z) --- compiler/src/dotty/tools/dotc/core/Denotations.scala | 12 ++++++------ compiler/src/dotty/tools/dotc/core/NameOps.scala | 2 +- .../src/dotty/tools/dotc/core/SymDenotations.scala | 12 ++++++------ compiler/src/dotty/tools/dotc/core/Types.scala | 6 +++--- .../dotty/tools/dotc/transform/Constructors.scala | 2 +- .../dotty/tools/dotc/transform/ParamForwarding.scala | 2 +- .../dotty/tools/dotc/transform/SuperAccessors.scala | 2 +- .../tools/dotc/transform/TransformByNameApply.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../dotty/tools/dotc/util/SimpleIdentityMap.scala | 2 +- .../repl/terminal/filters/ReadlineFilters.scala | 2 +- tests/neg/i2033.scala | 4 ++-- tests/run/collections.scala | 8 ++++---- tests/run/colltest1.scala | 12 ++++++------ tests/run/runtime.scala | 4 ++-- tests/run/t2544.scala | 8 ++++---- tests/run/t4813.scala | 4 ++-- tests/run/t5045.scala | 2 +- tests/run/t6271.scala | 2 +- tests/run/t6827.scala | 2 +- 21 files changed, 47 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 0443f346476e..1e6b8bf155f4 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -553,7 +553,7 @@ object Denotations { val r = mergeDenot(this, that) if (r.exists) r else MultiDenotation(this, that) case that @ MultiDenotation(denot1, denot2) => - this & (denot1, pre) & (denot2, pre) + this .& (denot1, pre) .& (denot2, pre) } } @@ -634,11 +634,11 @@ object Denotations { else if (!that.exists) that else this match { case denot1 @ MultiDenotation(denot11, denot12) => - denot1.derivedUnionDenotation(denot11 | (that, pre), denot12 | (that, pre)) + denot1.derivedUnionDenotation(denot11 .| (that, pre), denot12 .| (that, pre)) case denot1: SingleDenotation => that match { case denot2 @ MultiDenotation(denot21, denot22) => - denot2.derivedUnionDenotation(this | (denot21, pre), this | (denot22, pre)) + denot2.derivedUnionDenotation(this .| (denot21, pre), this .| (denot22, pre)) case denot2: SingleDenotation => unionDenot(denot1, denot2) } @@ -1180,7 +1180,7 @@ object Denotations { final case class DenotUnion(denot1: PreDenotation, denot2: PreDenotation) extends MultiPreDenotation { def exists = true def toDenot(pre: Type)(implicit ctx: Context) = - (denot1 toDenot pre) & (denot2 toDenot pre, pre) + (denot1 toDenot pre) .& (denot2 toDenot pre, pre) def containsSym(sym: Symbol) = (denot1 containsSym sym) || (denot2 containsSym sym) type AsSeenFromResult = PreDenotation @@ -1218,8 +1218,8 @@ object Denotations { def hasAltWith(p: SingleDenotation => Boolean): Boolean = denot1.hasAltWith(p) || denot2.hasAltWith(p) def accessibleFrom(pre: Type, superAccess: Boolean)(implicit ctx: Context): Denotation = { - val d1 = denot1 accessibleFrom (pre, superAccess) - val d2 = denot2 accessibleFrom (pre, superAccess) + val d1 = denot1.accessibleFrom(pre, superAccess) + val d2 = denot2.accessibleFrom(pre, superAccess) if (!d1.exists) d2 else if (!d2.exists) d1 else derivedUnionDenotation(d1, d2) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 6da58c4241e1..f48f3115fd7f 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -121,7 +121,7 @@ object NameOps { /** If flags is a ModuleClass but not a Package, add module class suffix */ def adjustIfModuleClass(flags: Flags.FlagSet): N = likeSpaced { - if (flags is (ModuleClass, butNot = Package)) name.asTypeName.moduleClassName + if (flags.is(ModuleClass, butNot = Package)) name.asTypeName.moduleClassName else name.toTermName.exclude(AvoidClashName) } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 3064078ed403..550cb47427d3 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -190,7 +190,7 @@ object SymDenotations { * in `butNot` are set? */ final def is(fs: FlagSet, butNot: FlagSet)(implicit ctx: Context) = - (if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags) is (fs, butNot) + (if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags).is(fs, butNot) /** Has this denotation all of the flags in `fs` set? */ final def is(fs: FlagConjunction)(implicit ctx: Context) = @@ -200,7 +200,7 @@ object SymDenotations { * in `butNot` are set? */ final def is(fs: FlagConjunction, butNot: FlagSet)(implicit ctx: Context) = - (if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags) is (fs, butNot) + (if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags).is(fs, butNot) /** The type info. * The info is an instance of TypeType iff this is a type denotation @@ -457,13 +457,13 @@ object SymDenotations { /** Is symbol known to not exist, or potentially not completed yet? */ final def unforcedIsAbsent(implicit ctx: Context): Boolean = myInfo == NoType || - (this is (ModuleVal, butNot = Package)) && moduleClass.unforcedIsAbsent + (this.is(ModuleVal, butNot = Package)) && moduleClass.unforcedIsAbsent /** Is symbol known to not exist? */ final def isAbsent(implicit ctx: Context): Boolean = { ensureCompleted() (myInfo `eq` NoType) || - (this is (ModuleVal, butNot = Package)) && moduleClass.isAbsent + (this.is(ModuleVal, butNot = Package)) && moduleClass.isAbsent } /** Is this symbol the root class or its companion object? */ @@ -921,7 +921,7 @@ object SymDenotations { * A local dummy owner is mapped to the primary constructor of the class. */ final def enclosingMethod(implicit ctx: Context): Symbol = - if (this is (Method, butNot = Label)) symbol + if (this.is(Method, butNot = Label)) symbol else if (this.isClass) primaryConstructor else if (this.exists) owner.enclosingMethod else NoSymbol @@ -1352,7 +1352,7 @@ object SymDenotations { // ----- denotation fields and accessors ------------------------------ - if (initFlags is (Module, butNot = Package)) + if (initFlags.is(Module, butNot = Package)) assert(name.is(ModuleClassName), s"module naming inconsistency: ${name.debugString}") /** The symbol asserted to have type ClassSymbol */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 01144c3ddbfd..8cb791f066c8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -598,7 +598,7 @@ object Types { else pdenot.info recoverable_& rinfo pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, jointInfo) } else { - pdenot & ( + pdenot .& ( new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId)), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) @@ -643,7 +643,7 @@ object Types { } def goAnd(l: Type, r: Type) = { - go(l) & (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) + go(l) .& (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) } val recCount = ctx.findMemberCount @@ -980,7 +980,7 @@ object Types { case res => res } case tp @ AndType(tp1, tp2) => - tp derived_& (tp1.widenUnion, tp2.widenUnion) + tp.derived_&(tp1.widenUnion, tp2.widenUnion) case tp: RefinedType => tp.derivedRefinedType(tp.parent.widenUnion, tp.refinedName, tp.refinedInfo) case tp: RecType => diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index 89a3d16772a4..6f25948aadb6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -152,7 +152,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case Ident(_) | Select(This(_), _) => var sym = tree.symbol - if (sym is (ParamAccessor, butNot = Mutable)) sym = sym.subst(accessors, paramSyms) + if (sym.is(ParamAccessor, butNot = Mutable)) sym = sym.subst(accessors, paramSyms) if (sym.owner.isConstructor) ref(sym).withPos(tree.pos) else tree case Apply(fn, Nil) => val fn1 = transform(fn) diff --git a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala index b876a3a9b47e..1e2824027f72 100644 --- a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala +++ b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala @@ -48,7 +48,7 @@ class ParamForwarding(thisPhase: DenotTransformer) { * } */ val candidate = sym.owner.asClass.superClass - .info.decl(sym.name).suchThat(_ is (ParamAccessor, butNot = Mutable)).symbol + .info.decl(sym.name).suchThat(_.is(ParamAccessor, butNot = Mutable)).symbol if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate else if (candidate.exists) inheritedAccessor(candidate) else NoSymbol diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index 1435a5aaf099..9d90683184f3 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -123,7 +123,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { // SI-4989 Check if an intermediate class between `clazz` and `sym.owner` redeclares the method as abstract. for (intermediateClass <- clazz.info.baseClasses.tail.takeWhile(_ != sym.owner)) { val overriding = sym.overridingSymbol(intermediateClass) - if ((overriding is (Deferred, butNot = AbsOverride)) && !(overriding.owner is Trait)) + if (overriding.is(Deferred, butNot = AbsOverride) && !overriding.owner.is(Trait)) ctx.error( s"${sym.showLocated} cannot be directly accessed from ${clazz} because ${overriding.owner} redeclares it as abstract", sel.pos) diff --git a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala index c0825c7e8bea..e34ba59a7523 100644 --- a/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala +++ b/compiler/src/dotty/tools/dotc/transform/TransformByNameApply.scala @@ -26,7 +26,7 @@ abstract class TransformByNameApply extends MiniPhase { thisPhase: DenotTransfor /** If denotation had an ExprType before, it now gets a function type */ protected def exprBecomesFunction(symd: SymDenotation)(implicit ctx: Context) = - (symd is Param) || (symd is (ParamAccessor, butNot = Method)) + symd.is(Param) || symd.is(ParamAccessor, butNot = Method) protected def isByNameRef(tree: Tree)(implicit ctx: Context) = { val origDenot = originalDenotation(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 78c9d111cf9e..f1a5a089b258 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1029,7 +1029,7 @@ class Namer { typer: Typer => def moduleValSig(sym: Symbol)(implicit ctx: Context): Type = { val clsName = sym.name.moduleClassName val cls = ctx.denotNamed(clsName) suchThat (_ is ModuleClass) - ctx.owner.thisType select (clsName, cls) + ctx.owner.thisType.select(clsName, cls) } /** The type signature of a ValDef or DefDef diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c667238ff0ac..250da0fd7ed5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -151,7 +151,7 @@ class Typer extends Namer def qualifies(denot: Denotation): Boolean = reallyExists(denot) && !(pt.isInstanceOf[UnapplySelectionProto] && - (denot.symbol is (Method, butNot = Accessor))) && + (denot.symbol.is(Method, butNot = Accessor))) && !(denot.symbol is PackageClass) /** Find the denotation of enclosing `name` in given context `ctx`. diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala index bdbba5605141..4606499fe211 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala @@ -22,7 +22,7 @@ abstract class SimpleIdentityMap[K <: AnyRef, +V >: Null <: AnyRef] extends (K = def toList: List[(K, V)] = map2((k, v) => (k, v)) override def toString = { def assocToString(key: K, value: V) = s"$key -> $value" - map2(assocToString) mkString ("(", ", ", ")") + map2(assocToString).mkString("(", ", ", ")") } } diff --git a/compiler/src/dotty/tools/repl/terminal/filters/ReadlineFilters.scala b/compiler/src/dotty/tools/repl/terminal/filters/ReadlineFilters.scala index c619238de2d0..f106c9a19510 100644 --- a/compiler/src/dotty/tools/repl/terminal/filters/ReadlineFilters.scala +++ b/compiler/src/dotty/tools/repl/terminal/filters/ReadlineFilters.scala @@ -121,7 +121,7 @@ object ReadlineFilters { } def cutCharLeft(b: Vector[Char], c: Int) = { /* Do not edit current cut. Zsh(zle) & Bash(readline) do not edit the yank ring for Ctrl-h */ - (b patch(from = c - 1, patch = Nil, replaced = 1), c - 1) + (b .patch(from = c - 1, patch = Nil, replaced = 1), c - 1) } def cutLineLeft(b: Vector[Char], c: Int) = { diff --git a/tests/neg/i2033.scala b/tests/neg/i2033.scala index b28a0d99e5be..c717bc8679e6 100644 --- a/tests/neg/i2033.scala +++ b/tests/neg/i2033.scala @@ -4,10 +4,10 @@ object Test { def check(obj: AnyRef): Unit = { val bos = new ByteArrayOutputStream() val out = new ObjectOutputStream(println) // error - val arr = bos toByteArray () + val arr = bos.toByteArray () val in = (()) val deser = () - val lhs = mutable LinkedHashSet () + val lhs = mutable.LinkedHashSet () check(lhs) } } diff --git a/tests/run/collections.scala b/tests/run/collections.scala index acc2d93fff82..2b90655a1deb 100644 --- a/tests/run/collections.scala +++ b/tests/run/collections.scala @@ -18,7 +18,7 @@ object Test extends dotty.runtime.LegacyApp { println("***** "+msg+":") var s = s0 s = s + 2 - s = s + (3, 4000, 10000) + s = s.+(3, 4000, 10000) println("test1: "+sum(s)) time { s = s ++ (List.range(0, iters) map (2*)) @@ -36,7 +36,7 @@ object Test extends dotty.runtime.LegacyApp { println("***** "+msg+":") var s = s0 s = s + 2 - s = s + (3, 4000, 10000) + s = s.+(3, 4000, 10000) println("test1: "+sum(s)) time { s = s ++ (List.range(0, iters) map (2*)) @@ -54,7 +54,7 @@ object Test extends dotty.runtime.LegacyApp { println("***** "+msg+":") var s = s0 s = s + (2 -> 2) - s = s + (3 -> 3, 4000 -> 4000, 10000 -> 10000) + s = s.+(3 -> 3, 4000 -> 4000, 10000 -> 10000) println("test1: "+sum(s map (_._2))) time { s = s ++ (List.range(0, iters) map (x => x * 2 -> x * 2)) @@ -89,7 +89,7 @@ object Test extends dotty.runtime.LegacyApp { println("***** "+msg+":") var s = s0 s = s + (2 -> 2) - s = s + (3 -> 3, 4000 -> 4000, 10000 -> 10000) + s = s.+(3 -> 3, 4000 -> 4000, 10000 -> 10000) println("test1: "+sum(s map (_._2))) time { s = s ++ (List.range(0, iters) map (x => x * 2 -> x * 2)) diff --git a/tests/run/colltest1.scala b/tests/run/colltest1.scala index ab519067b6f7..612b3e8c1c79 100644 --- a/tests/run/colltest1.scala +++ b/tests/run/colltest1.scala @@ -47,8 +47,8 @@ object Test extends dotty.runtime.LegacyApp { assert(vs1 == ten) assert((ten take 5) == firstFive) assert((ten drop 5) == secondFive) - assert(ten slice (3, 3) isEmpty) - assert((ten slice (3, 6)) == List(4, 5, 6), ten slice (3, 6)) + assert(ten.slice(3, 3) isEmpty) + assert((ten.slice(3, 6)) == List(4, 5, 6), ten .slice (3, 6)) assert((ten takeWhile (_ <= 5)) == firstFive) assert((ten dropWhile (_ <= 5)) == secondFive) assert((ten span (_ <= 5)) == (firstFive, secondFive)) @@ -139,7 +139,7 @@ object Test extends dotty.runtime.LegacyApp { def setTest(empty: => Set[String]): Unit = { var s = empty + "A" + "B" + "C" - s += ("D", "E", "F") + s = s.+("D", "E", "F") s ++= List("G", "H", "I") s ++= ('J' to 'Z') map (_.toString) assert(s forall (s contains)) @@ -156,8 +156,8 @@ object Test extends dotty.runtime.LegacyApp { assert(!s.isEmpty) val s1 = s intersect empty assert(s1 == empty, s1) - def abc = empty + ("a", "b", "c") - def bc = empty + ("b", "c") + def abc = empty.+("a", "b", "c") + def bc = empty.+("b", "c") assert(bc subsetOf abc) } @@ -173,7 +173,7 @@ object Test extends dotty.runtime.LegacyApp { def mapTest(empty: => Map[String, String]) = { var m = empty + ("A" -> "A") + ("B" -> "B") + ("C" -> "C") - m += (("D" -> "D"), ("E" -> "E"), ("F" -> "F")) + m = m.+(("D" -> "D"), ("E" -> "E"), ("F" -> "F")) m ++= List(("G" -> "G"), ("H" -> "H"), ("I" -> "I")) m ++= ('J' to 'Z') map (x => (x.toString -> x.toString)) println(m.toList.sorted) diff --git a/tests/run/runtime.scala b/tests/run/runtime.scala index 89348b294db8..3a55069c3f0c 100644 --- a/tests/run/runtime.scala +++ b/tests/run/runtime.scala @@ -65,7 +65,7 @@ object Test1Test { // {System.out.print(12); java.lang}.System.out.println(); // {System.out.print(13); java.lang.System}.out.println(); {Console.print(14); Console}.println; - {Console.print(15); (() => Console.println):(() => Unit)} apply (); + {Console.print(15); (() => Console.println):(() => Unit)} .apply (); {Console.print(16); Console.println}; {Console.print(20)}; test1.bar.System.out.println(); @@ -73,7 +73,7 @@ object Test1Test { // {System.out.print(22); test1.bar}.System.out.println(); {Console.print(23); test1.bar.System}.out.println(); {Console.print(24); test1.bar.System.out}.println(); - {Console.print(25); test1.bar.System.out.println:(() => Unit)} apply (); + {Console.print(25); test1.bar.System.out.println:(() => Unit)} .apply (); {Console.print(26); test1.bar.System.out.println()}; } diff --git a/tests/run/t2544.scala b/tests/run/t2544.scala index 6bee2f108230..ff53cf4f18d7 100644 --- a/tests/run/t2544.scala +++ b/tests/run/t2544.scala @@ -11,10 +11,10 @@ object Test { ) def main(args: Array[String]) = { - println(Foo indexWhere(_ >= 2,1)) - println(Foo.toList indexWhere(_ >= 2,1)) - println(Foo segmentLength(_ <= 3,1)) - println(Foo.toList segmentLength(_ <= 3,1)) + println(Foo .indexWhere(_ >= 2,1)) + println(Foo.toList .indexWhere(_ >= 2,1)) + println(Foo .segmentLength(_ <= 3,1)) + println(Foo.toList .segmentLength(_ <= 3,1)) lengthEquiv(Foo lengthCompare 7) lengthEquiv(Foo.toList lengthCompare 7) lengthEquiv(Foo lengthCompare 2) diff --git a/tests/run/t4813.scala b/tests/run/t4813.scala index 1146bb16ec04..29fa71c5608f 100644 --- a/tests/run/t4813.scala +++ b/tests/run/t4813.scala @@ -31,7 +31,7 @@ object Test extends dotty.runtime.LegacyApp { runTest(TreeSet(1,2,3))(_.clone) { buf => buf add 4 } // Maps - runTest(HashMap(1->1,2->2,3->3))(_.clone) { buf => buf put (4,4) } - runTest(WeakHashMap(1->1,2->2,3->3))(_.clone) { buf => buf put (4,4) } + runTest(HashMap(1->1,2->2,3->3))(_.clone) { buf => buf.put(4,4) } + runTest(WeakHashMap(1->1,2->2,3->3))(_.clone) { buf => buf.put(4,4) } } diff --git a/tests/run/t5045.scala b/tests/run/t5045.scala index 3778339aa4b1..ef992da813f5 100644 --- a/tests/run/t5045.scala +++ b/tests/run/t5045.scala @@ -6,7 +6,7 @@ object Test extends dotty.runtime.LegacyApp { import scala.util.matching.{ Regex, UnanchoredRegex } val dateP1 = """(\d\d\d\d)-(\d\d)-(\d\d)""".r.unanchored - val dateP2 = """(\d\d\d\d)-(\d\d)-(\d\d)""" r ("year", "month", "day") unanchored + val dateP2 = """(\d\d\d\d)-(\d\d)-(\d\d)""" .r ("year", "month", "day") unanchored val dateP3 = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day") with UnanchoredRegex val yearStr = "2011" diff --git a/tests/run/t6271.scala b/tests/run/t6271.scala index e8047a9be7f2..a7b60b1df7ba 100644 --- a/tests/run/t6271.scala +++ b/tests/run/t6271.scala @@ -21,7 +21,7 @@ object Test extends dotty.runtime.LegacyApp { } def slicedIssue = { val viewed : Iterable[Iterable[Int]] = List(List(0).view).view - val filtered = viewed flatMap { x => List( x slice (2,3) ) } + val filtered = viewed flatMap { x => List( x.slice(2,3) ) } filtered.iterator.toIterable.flatten } filterIssue diff --git a/tests/run/t6827.scala b/tests/run/t6827.scala index 43e01831c340..7c9c7057aa62 100644 --- a/tests/run/t6827.scala +++ b/tests/run/t6827.scala @@ -10,7 +10,7 @@ object Test extends App { } catch { case e: Exception => e.toString } - println("%s: %s" format (label, status)) + println("%s: %s".format(label, status)) } tryit("start at -5", -5, 10) From b7d33c6acd897edc6df05865b4410d504e664c61 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 15 Apr 2018 12:02:43 +0200 Subject: [PATCH 4/7] Migration warning for non-unary symbolic operators Under Scala-2 mode, issue a migration warning for a symbolic method that is not unary. Also: Two more tests updated to new infix operator regime. --- .../src/dotty/tools/dotc/core/NameOps.scala | 6 +++-- .../src/dotty/tools/dotc/typer/Checking.scala | 24 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 ++ tests/pos/Map.scala | 2 +- tests/run/iterators.scala | 4 ++-- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index f48f3115fd7f..7a99f20bb2c9 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -87,8 +87,10 @@ object NameOps { } } - def isOpName = name match { - case name: SimpleName => NameTransformer.encode(name) != name + /** Is name an operator name that does not start with a letter or `_` or `$`? */ + def isSymbolic = name match { + case name: SimpleName => + !Chars.isIdentifierStart(name.head) && NameTransformer.encode(name) != name case _ => false } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 698fdaff0c15..13250d2b2c16 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -850,6 +850,29 @@ trait Checking { checkEnumCaseRefsLegal(stat, enumContext) stats } + + /** `tp` refers to a method with exactly one parameter in its first parameter list. + * If `tp` is overloaded, all alternatives are unary in that sense. + */ + def isUnary(tp: Type)(implicit ctx: Context): Boolean = tp match { + case tp: MethodicType => + tp.firstParamTypes match { + case ptype :: Nil => !ptype.isRepeatedParam + case _ => false + } + case tp: TermRef => + tp.denot.alternatives.forall(alt => isUnary(alt.info)) + case _ => + false + } + + /** Under Scala-2 mode, issue a migration warning for a symbolic method that is + * not unary. + */ + def checkSymbolicUnary(sym: Symbol)(implicit ctx: Context): Unit = + if (sym.name.isSymbolic && !isUnary(sym.info) && ctx.scala2Mode) + ctx.migrationWarning(i"""symbolic operator `${sym.name}` should take exactly one parameter, + |otherwise it cannot be used in infix position.""", sym.pos) } trait ReChecking extends Checking { @@ -877,4 +900,5 @@ trait NoChecking extends ReChecking { override def checkCaseInheritance(parentSym: Symbol, caseCls: ClassSymbol, pos: Position)(implicit ctx: Context) = () override def checkNoForwardDependencies(vparams: List[ValDef])(implicit ctx: Context): Unit = () override def checkMembersOK(tp: Type, pos: Position)(implicit ctx: Context): Type = tp + override def checkSymbolicUnary(sym: Symbol)(implicit ctx: Context): Unit = () } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 250da0fd7ed5..30293593833d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1434,6 +1434,8 @@ class Typer extends Namer for (param <- tparams1 ::: vparamss1.flatten) checkRefsLegal(param, sym.owner, (name, sym) => sym.is(TypeParam), "secondary constructor") + checkSymbolicUnary(sym) + assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) //todo: make sure dependent method types do not depend on implicits or by-name params } diff --git a/tests/pos/Map.scala b/tests/pos/Map.scala index 5178d5a862cf..b5ba3b1627a6 100644 --- a/tests/pos/Map.scala +++ b/tests/pos/Map.scala @@ -176,7 +176,7 @@ object Map extends ImmutableMapFactory[Map] { else if (key == key2) new Map4(key1, value1, key2, value, key3, value3, key4, value4) else if (key == key3) new Map4(key1, value1, key2, value2, key3, value, key4, value4) else if (key == key4) new Map4(key1, value1, key2, value2, key3, value3, key4, value) - else new HashMap + ((key1, value1), (key2, value2), (key3, value3), (key4, value4), (key, value)) + else HashMap((key1, value1), (key2, value2), (key3, value3), (key4, value4), (key, value)) def + [B1 >: B](kv: (A, B1)): Map[A, B1] = updated(kv._1, kv._2) def - (key: A): Map[A, B] = if (key == key1) new Map3(key2, value2, key3, value3, key4, value4) diff --git a/tests/run/iterators.scala b/tests/run/iterators.scala index 2f70abd52193..a0696eace072 100644 --- a/tests/run/iterators.scala +++ b/tests/run/iterators.scala @@ -57,8 +57,8 @@ object Test { def check_drop: Int = { val it1 = Iterator.from(0) val it2 = it1 map { 2 * _ } - val n1 = it1 drop 2 next() - val n2 = it2 drop 2 next(); + val n1 = (it1 drop 2) .next() + val n2 = (it2 drop 2) .next(); n1 + n2 } From fd6894572881a9a1bce48edb6fc66946844c18e3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 15 Apr 2018 12:13:17 +0200 Subject: [PATCH 5/7] New scheme for infix operators - An infix operation `x op (y, z)` now unconditionally interprets `(y, z)` as a tuple, unless in Scala-2 mode. - In Scala-2 mode, the pattern will give a migration warning with option to rewrite in most cases. Excluded from rewrite are operator assignments and right-associative infix operations. - Auto-tupling is only enabled in `Scala-2` mode, and will give a migration warning with option to rewrite in that mode. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 16 ++-- .../dotty/tools/dotc/transform/Erasure.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 73 +++++++++++++++++-- .../src/dotty/tools/dotc/typer/Inliner.scala | 4 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 5 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 28 ++----- 6 files changed, 88 insertions(+), 40 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 30b4ef2bd284..c0508c81d168 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -776,18 +776,16 @@ object desugar { * ==> r.op(l) if op is right-associative */ def binop(left: Tree, op: Ident, right: Tree)(implicit ctx: Context): Apply = { - def assignToNamedArg(arg: Tree) = arg match { + + def assignToNamedArg(arg: Tree): Tree = arg match { case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs) + case Parens(arg1) => untpd.cpy.Parens(arg)(assignToNamedArg(arg1)) + case Tuple(args) => untpd.cpy.Tuple(arg)(args.mapConserve(assignToNamedArg)) case _ => arg } - def makeOp(fn: Tree, arg: Tree, selectPos: Position) = { - val args: List[Tree] = arg match { - case Parens(arg) => assignToNamedArg(arg) :: Nil - case Tuple(args) => args.mapConserve(assignToNamedArg) - case _ => arg :: Nil - } - Apply(Select(fn, op.name).withPos(selectPos), args) - } + + def makeOp(fn: Tree, arg: Tree, selectPos: Position) = + Apply(Select(fn, op.name).withPos(selectPos), assignToNamedArg(arg)) if (isLeftAssoc(op.name)) makeOp(left, right, Position(left.pos.start, op.pos.end, op.pos.start)) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index d0f7daa2cadc..fa3d10a645e1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -481,7 +481,7 @@ object Erasure { /** Besides normal typing, this method collects all arguments * to a compacted function into a single argument of array type. */ - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { + override def typedApply(tree: untpd.Apply, pt: Type, scala2InfixOp: Boolean)(implicit ctx: Context): Tree = { val Apply(fun, args) = tree if (fun.symbol == defn.cbnArg) typedUnadapted(args.head, pt) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index bc7bb90cc70d..5707f77ef20e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -23,6 +23,7 @@ import StdNames._ import NameKinds.DefaultGetterName import ProtoTypes._ import Inferencing._ +import rewrite.Rewrites.patch import collection.mutable import config.Printers.{overload, typr, unapp} @@ -242,7 +243,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def success = ok protected def methodType = methType.asInstanceOf[MethodType] - private def methString: String = i"${methRef.symbol}: ${methType.show}" + private def methString: String = i"${methRef.symbol.showLocated}: ${methType.show}" /** Re-order arguments to correctly align named arguments */ def reorder[T >: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = { @@ -669,10 +670,19 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * or, if application is an operator assignment, also an `Assign` or * Block node. */ - def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { - + def typedApply(tree: untpd.Apply, pt: Type, scala2InfixOp: Boolean = false)(implicit ctx: Context): Tree = { def realApply(implicit ctx: Context): Tree = track("realApply") { - val originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree)) + var originalProto = new FunProto(tree.args, IgnoredProto(pt), this)(argCtx(tree)) + + // In an infix operation `x op (y, z)` under Scala-2 mode, rewrite tuple on the + // right hand side to multi-parameters, i.e `x.op(y, z)`, relying on auto-tupling + // to revert this if `op` takes a single argument. + tree.args match { + case untpd.Tuple(elems) :: Nil if scala2InfixOp => + originalProto = new FunProto(elems, IgnoredProto(pt), this)(argCtx(tree)) + originalProto.scala2InfixOp = true + case _ => + } val fun1 = typedExpr(tree.fun, originalProto) // Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as @@ -682,7 +692,41 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // otherwise we would get possible cross-talk between different `adapt` calls using the same // prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with // a modified tree but this would be more convoluted and less efficient. - val proto = if (originalProto.isTupled) originalProto.tupled else originalProto + // All of this is provisional in the long run, since it is only needed under Scala-2 mode. + val proto = + if (originalProto.isTupled) originalProto.tupled + else { + // If we see a multi-parameter infix operation `x op (y, z)` under Scala-2 mode, + // issue a migration warning and propose a rewrite to `x .op (y, z)` (or + // `(x) .op (y, z)` is `x` is itself an infix operation) where this is possible. + // Not possible are rewrites of operator assignments and right-associative operators. + tree.fun match { + case sel @ Select(qual, opName) + if originalProto.scala2InfixOp && !fun1.tpe.widen.isErroneous => + val opEndOffset = sel.pos.point + opName.toString.length + val isOpAssign = ctx.source(opEndOffset) == '=' + val isRightAssoc = !isLeftAssoc(opName) + val addendum = + if (isOpAssign) "\nThis requires a manual rewrite since it is part of an operator assignment." + else if (isRightAssoc) "\nThis requires a manual rewrite since the operator is right-associative." + else "\nThis can be fixed automatically using -rewrite." + ctx.migrationWarning( + em"""infix operator takes exactly one argument, + |use method call syntax with `.` instead.$addendum""", fun1.pos) + val Select(qual, _) = tree.fun + if (!isOpAssign && !isRightAssoc) { + patch(Position(sel.pos.point), ".") + qual match { + case qual: untpd.InfixOp => + patch(qual.pos.startPos, "(") + patch(qual.pos.endPos, ")") + case _ => + } + } + case _ => + } + originalProto + } // If some of the application's arguments are function literals without explicitly declared // parameter types, relate the normalized result type of the application with the @@ -750,12 +794,25 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * { val xs = es; e' = e' + args } */ def typedOpAssign(implicit ctx: Context): Tree = track("typedOpAssign") { - val Apply(Select(lhs, name), rhss) = tree + val Apply(sel @ Select(lhs, name), rhss) = tree val lhs1 = typedExpr(lhs) val liftedDefs = new mutable.ListBuffer[Tree] val lhs2 = untpd.TypedSplice(LiftComplex.liftAssigned(liftedDefs, lhs1)) - val assign = untpd.Assign(lhs2, - untpd.Apply(untpd.Select(lhs2, name.asSimpleName.dropRight(1)), rhss)) + val opName = name.asSimpleName.dropRight(1) + + // If original operation was an infix operation under Scala-2 mode, generate + // again an infix operation insteadof an application, so that the logic does + // the right thing for migrating from auto-tupling and multi-parameter infix ops. + val app = rhss match { + case (rhs: untpd.Tuple) :: Nil if scala2InfixOp => + val opEndOffset = sel.pos.point + opName.length + assert(ctx.source(opEndOffset) == '=') + val op = untpd.Ident(opName).withPos(Position(sel.pos.point, opEndOffset)) + untpd.InfixOp(lhs2, op, rhs) + case _ => + untpd.Apply(untpd.Select(lhs2, opName), rhss) + } + val assign = untpd.Assign(lhs2, app) wrapDefs(liftedDefs, typed(assign)) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 49a507e0994c..80aec515be66 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -564,13 +564,13 @@ class Inliner(call: tpd.Tree, rhs: tpd.Tree)(implicit ctx: Context) { } } - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = + override def typedApply(tree: untpd.Apply, pt: Type, scala2InfixOp: Boolean)(implicit ctx: Context) = tree.asInstanceOf[tpd.Tree] match { case Apply(Select(InlineableArg(closure(_, fn, _)), nme.apply), args) => inlining.println(i"reducing $tree with closure $fn") typed(fn.appliedToArgs(args), pt) case _ => - super.typedApply(tree, pt) + super.typedApply(tree, pt, scala2InfixOp) } } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index f06df5d03d74..9248edde5c4a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -288,6 +288,11 @@ object ProtoTypes { def typeOfArg(arg: untpd.Tree)(implicit ctx: Context): Type = myTypedArg(arg).tpe + // `scala2InfixOp` and `tupled` only needed for Scala-2 compatibility and migration + + /** FunProto applies to an infix operation under -language:Scala2 */ + var scala2InfixOp = false + private[this] var myTupled: Type = NoType /** The same proto-type but with all arguments combined in a single tuple */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 30293593833d..24eea10ef8cd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1709,7 +1709,7 @@ class Typer extends Namer */ def typedInfixOp(tree: untpd.InfixOp, pt: Type)(implicit ctx: Context): Tree = { val untpd.InfixOp(l, op, r) = tree - val app = typedApply(desugar.binop(l, op, r), pt) + val app = typedApply(desugar.binop(l, op, r), pt, scala2InfixOp = ctx.scala2Mode) if (untpd.isLeftAssoc(op.name)) app else { val defs = new mutable.ListBuffer[Tree] @@ -2037,22 +2037,13 @@ class Typer extends Namer def canAutoTuple(args: List[untpd.Tree])(implicit ctx: Context): Boolean = { val pos = Position(args.map(_.pos.start).min, args.map(_.pos.end).max) ctx.testScala2Mode( - i"""auto-tupling is no longer supported in this case, - |arguments now need to be enclosed in parentheses (...).""", + i"""auto-tupling is no longer supported, + |arguments now need to be enclosed in parentheses (...).""", pos, { patch(pos.startPos, "("); patch(pos.endPos, ")") }) } - def takesAutoTupledArgs(tp: Type, sym: Symbol, args: List[untpd.Tree])(implicit ctx: Context): Boolean = tp match { - case tp: MethodicType => - tp.firstParamTypes match { - case ptype :: Nil => !ptype.isRepeatedParam && (sym.name.isOpName || canAutoTuple(args)) - case _ => false - } - case tp: TermRef => - tp.denot.alternatives.forall(alt => takesAutoTupledArgs(alt.info, alt.symbol, args)) - case _ => - false - } + def takesAutoTupledArgs(tp: Type, pt: FunProto)(implicit ctx: Context): Boolean = + pt.args.lengthCompare(1) > 0 && isUnary(tp) && (pt.scala2InfixOp || canAutoTuple(pt.args)) /** Perform the following adaptations of expression, pattern or type `tree` wrt to * given prototype `pt`: @@ -2143,10 +2134,8 @@ class Typer extends Namer def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { case _: MethodOrPoly => - if (pt.args.lengthCompare(1) > 0 && takesAutoTupledArgs(wtp, tree.symbol, pt.args)) - adapt(tree, pt.tupled, locked) - else - tree + if (takesAutoTupledArgs(wtp, pt)) adapt(tree, pt.tupled, locked) + else tree case _ => tryInsertApplyOrImplicit(tree, pt, locked) { errorTree(tree, MethodDoesNotTakeParameters(tree, methPart(tree).tpe)(err)) } @@ -2511,8 +2500,7 @@ class Typer extends Namer tree case ref: TermRef => // this case can happen in case tree.tpe is overloaded pt match { - case pt: FunProto - if pt.args.lengthCompare(1) > 0 && takesAutoTupledArgs(ref, ref.symbol, pt.args) => + case pt: FunProto if takesAutoTupledArgs(ref, pt) => adapt(tree, pt.tupled, locked) case _ => adaptOverloaded(ref) From 429128f120b40c8bcc0d536bf28f818f4a676ca3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 15 Apr 2018 12:33:17 +0200 Subject: [PATCH 6/7] Cleanups - add some comments - revert some accidental rewritings --- .../src/dotty/tools/dotc/core/Definitions.scala | 4 ++++ .../src/dotty/tools/dotc/reporting/trace.scala | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 7260aa1b34a8..f5eefa75c57c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -878,6 +878,10 @@ class Definitions { name.length > prefix.length && name.drop(prefix.length).forall(_.isDigit)) + // Currently unused: + /** If `cls` has name s"$prefix$digits" where $digits is a valid integer, that + * that integer, otherwise -1. + */ def arity(cls: Symbol, prefix: String): Int = scalaClassName(cls).applySimple(-1) { name => if (name.startsWith(prefix)) { diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index 6e34c6f1188d..d42008500bbf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -9,40 +9,40 @@ import core.Mode object trace { - @`inline` + @inline def onDebug[TD](question: => String)(op: => TD)(implicit ctx: Context): TD = conditionally(ctx.settings.YdebugTrace.value, question, false)(op) - @`inline` + @inline def conditionally[TC](cond: Boolean, question: => String, show: Boolean)(op: => TC)(implicit ctx: Context): TC = { def op1 = op if (Config.tracingEnabled && cond) apply[TC](question, Printers.default, show)(op1) else op1 } - @`inline` + @inline def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T = { def op1 = op if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 else doTrace[T](question, printer, showOp)(op1) } - @`inline` + @inline def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { def op1 = op if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1) } - @`inline` + @inline def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = apply[T](question, printer, false)(op) - @`inline` + @inline def apply[T](question: => String, show: Boolean)(op: => T)(implicit ctx: Context): T = apply[T](question, Printers.default, show)(op) - @`inline` + @inline def apply[T](question: => String)(op: => T)(implicit ctx: Context): T = apply[T](question, Printers.default, false)(op) @@ -59,7 +59,7 @@ object trace { (op: => T)(implicit ctx: Context): T = { // Avoid evaluating question multiple time, since each evaluation // may cause some extra logging output. - @volatile lazy val q: String = question + lazy val q: String = question apply[T](s"==> $q?", (res: Any) => s"<== $q = ${showOp(res)}")(op) } From 326abd7f76c9ffcb1a304679f8549dfcc64c63d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 15 Apr 2018 17:59:39 +0200 Subject: [PATCH 7/7] Adapt testing framework to new infix scheme --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 10 ++++++---- .../test/dotty/tools/vulpix/TestConfiguration.scala | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 945d5fa3efb4..829ca85d52cc 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -331,7 +331,7 @@ trait ParallelTesting extends RunnerOrchestration { self => protected def compile(files0: Array[JFile], flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { - val flags = flags0 and ("-d", targetDir.getAbsolutePath) + val flags = flags0.and("-d", targetDir.getAbsolutePath) def flattenFiles(f: JFile): Array[JFile] = if (f.isDirectory) f.listFiles.flatMap(flattenFiles) @@ -400,7 +400,7 @@ trait ParallelTesting extends RunnerOrchestration { self => protected def compileFromTasty(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { val tastyOutput = new JFile(targetDir.getPath + "_from-tasty") tastyOutput.mkdir() - val flags = flags0 and ("-d", tastyOutput.getAbsolutePath) and "-from-tasty" + val flags = flags0.and("-d", tastyOutput.getAbsolutePath).and("-from-tasty") def hasTastyFileToClassName(f: JFile): String = targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.') @@ -427,8 +427,10 @@ trait ParallelTesting extends RunnerOrchestration { self => val decompilationOutput = new JFile(targetDir.getPath) decompilationOutput.mkdir() val flags = - flags0 and ("-d", decompilationOutput.getAbsolutePath) and - "-decompile" and "-pagewidth" and "80" + flags0 + .and("-d", decompilationOutput.getAbsolutePath) + .and("-decompile") + .and("-pagewidth", "80") def hasTastyFileToClassName(f: JFile): String = targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.') diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 845908be2e0f..0bd8d5469c58 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -49,18 +49,18 @@ object TestConfiguration { val basicDefaultOptions = checkOptions ++ noCheckOptions ++ yCheckOptions val defaultUnoptimised = TestFlags(classPath, runClassPath, basicDefaultOptions) - val defaultOptimised = defaultUnoptimised and "-optimise" + val defaultOptimised = defaultUnoptimised.and("-optimise") val defaultOptions = defaultUnoptimised val defaultRunWithCompilerOptions = defaultOptions.withRunClasspath(Jars.dottyRunWithCompiler.mkString(":")) val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes" val allowDoubleBindings = defaultOptions without "-Yno-double-bindings" - val picklingOptions = defaultUnoptimised and ( + val picklingOptions = defaultUnoptimised.and( "-Xprint-types", "-Ytest-pickler", "-Yprint-pos", "-Yprint-pos-syms" ) - val scala2Mode = defaultOptions and "-language:Scala2" - val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") - val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") + val scala2Mode = defaultOptions.and("-language:Scala2") + val explicitUTF8 = defaultOptions.and("-encoding", "UTF8") + val explicitUTF16 = defaultOptions.and("-encoding", "UTF16") }