diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 1ab82058155f..af0f81441362 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -72,9 +72,12 @@ object desugar { val defctx = ctx.outersIterator.dropWhile(_.scope eq ctx.scope).next var local = defctx.denotNamed(tp.name).suchThat(_ is ParamOrAccessor).symbol if (local.exists) (defctx.owner.thisType select local).dealias - else throw new java.lang.Error( - s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}" - ) + else { + def msg = + s"no matching symbol for ${tp.symbol.showLocated} in ${defctx.owner} / ${defctx.effectiveScope}" + if (ctx.reporter.errorsReported) new ErrorType(msg) + else throw new java.lang.Error(msg) + } case _ => mapOver(tp) } @@ -124,7 +127,7 @@ object desugar { else vdef } - def makeImplicitParameters(tpts: List[Tree], forPrimaryConstructor: Boolean)(implicit ctx: Context) = + def makeImplicitParameters(tpts: List[Tree], forPrimaryConstructor: Boolean = false)(implicit ctx: Context) = for (tpt <- tpts) yield { val paramFlags: FlagSet = if (forPrimaryConstructor) PrivateLocalParamAccessor else Param val epname = EvidenceParamName.fresh() @@ -265,7 +268,7 @@ object desugar { val mods = cdef.mods val companionMods = mods .withFlags((mods.flags & AccessFlags).toCommonFlags) - .withMods(mods.mods.filter(!_.isInstanceOf[Mod.EnumCase])) + .withMods(Nil) val (constr1, defaultGetters) = defDef(constr0, isPrimaryConstructor = true) match { case meth: DefDef => (meth, Nil) @@ -291,7 +294,7 @@ object desugar { val isCaseClass = mods.is(Case) && !mods.is(Module) val isCaseObject = mods.is(Case) && mods.is(Module) - val isEnum = mods.hasMod[Mod.Enum] + val isEnum = mods.hasMod[Mod.Enum] && !mods.is(Module) val isEnumCase = isLegalEnumCase(cdef) val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. @@ -326,10 +329,12 @@ object desugar { val classTycon: Tree = new TypeRefTree // watching is set at end of method - def appliedRef(tycon: Tree) = - (if (constrTparams.isEmpty) tycon - else AppliedTypeTree(tycon, constrTparams map refOfDef)) - .withPos(cdef.pos.startPos) + def appliedTypeTree(tycon: Tree, args: List[Tree]) = + (if (args.isEmpty) tycon else AppliedTypeTree(tycon, args)) + .withPos(cdef.pos.startPos) + + def appliedRef(tycon: Tree, tparams: List[TypeDef] = constrTparams) = + appliedTypeTree(tycon, tparams map refOfDef) // a reference to the class type bound by `cdef`, with type parameters coming from the constructor val classTypeRef = appliedRef(classTycon) @@ -344,8 +349,7 @@ object desugar { else { ctx.error(i"explicit extends clause needed because type parameters of case and enum class differ" , cdef.pos.startPos) - AppliedTypeTree(enumClassRef, constrTparams map (_ => anyRef)) - .withPos(cdef.pos.startPos) + appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef)) } case _ => enumClassRef @@ -411,6 +415,31 @@ object desugar { if (isEnum) parents1 = parents1 :+ ref(defn.EnumType) + // The Eq instance for an Enum class. For an enum class + // + // enum class C[T1, ..., Tn] + // + // we generate: + // + // implicit def eqInstance[T1$1, ..., Tn$1, T1$2, ..., Tn$2](implicit + // ev1: Eq[T1$1, T1$2], ..., evn: Eq[Tn$1, Tn$2]]) + // : Eq[C[T1$1, ..., Tn$1], C[T1$2, ..., Tn$2]] = Eq + def eqInstance = { + def append(tdef: TypeDef, str: String) = cpy.TypeDef(tdef)(name = tdef.name ++ str) + val leftParams = derivedTparams.map(append(_, "$1")) + val rightParams = derivedTparams.map(append(_, "$2")) + val subInstances = (leftParams, rightParams).zipped.map((param1, param2) => + appliedRef(ref(defn.EqType), List(param1, param2))) + DefDef( + name = nme.eqInstance, + tparams = leftParams ++ rightParams, + vparamss = List(makeImplicitParameters(subInstances)), + tpt = appliedTypeTree(ref(defn.EqType), + appliedRef(classTycon, leftParams) :: appliedRef(classTycon, rightParams) :: Nil), + rhs = ref(defn.EqModule.termRef)).withFlags(Synthetic | Implicit) + } + def eqInstances = if (isEnum) eqInstance :: Nil else Nil + // The thicket which is the desugared version of the companion object // synthetic object C extends parentTpt { defs } def companionDefs(parentTpt: Tree, defs: List[Tree]) = @@ -420,6 +449,8 @@ object desugar { .withMods(companionMods | Synthetic)) .withPos(cdef.pos).toList + val companionMeths = defaultGetters ::: eqInstances + // The companion object definitions, if a companion is needed, Nil otherwise. // companion definitions include: // 1. If class is a case class case class C[Ts](p1: T1, ..., pN: TN)(moreParams): @@ -465,10 +496,10 @@ object desugar { DefDef(nme.unapply, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS) .withMods(synthetic) } - companionDefs(parent, applyMeths ::: unapplyMeth :: defaultGetters) + companionDefs(parent, applyMeths ::: unapplyMeth :: companionMeths) } - else if (defaultGetters.nonEmpty) - companionDefs(anyRef, defaultGetters) + else if (companionMeths.nonEmpty) + companionDefs(anyRef, companionMeths) else if (isValueClass) { constr0.vparamss match { case List(_ :: Nil) => companionDefs(anyRef, Nil) @@ -739,7 +770,7 @@ object desugar { } def makeImplicitFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = { - val params = makeImplicitParameters(formals.map(TypeTree), forPrimaryConstructor = false) + val params = makeImplicitParameters(formals.map(TypeTree)) new ImplicitFunction(params, body) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 7d8786bd5a4a..ecaa37bbe6be 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -547,6 +547,7 @@ class Definitions { lazy val EqType = ctx.requiredClassRef("scala.Eq") def EqClass(implicit ctx: Context) = EqType.symbol.asClass + def EqModule(implicit ctx: Context) = EqClass.companionModule lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 60c6a6ed06c0..cc057b98ee4f 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -400,6 +400,7 @@ object StdNames { val ensureAccessible : N = "ensureAccessible" val enumTag: N = "enumTag" val eq: N = "eq" + val eqInstance: N = "eqInstance" val equalsNumChar : N = "equalsNumChar" val equalsNumNum : N = "equalsNumNum" val equalsNumObject : N = "equalsNumObject" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index eeaabcdc3812..56646848bab3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -656,7 +656,7 @@ trait Implicits { self: Typer => if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) { val res = inferImplicitArg( defn.EqType.appliedTo(ltp, rtp), msgFun => ctx.error(msgFun(""), pos), pos) - implicits.println(i"Eq witness found: $res: ${res.tpe}") + implicits.println(i"Eq witness found for $ltp / $rtp: $res: ${res.tpe}") } /** Find an implicit parameter or conversion. @@ -676,7 +676,7 @@ trait Implicits { self: Typer => val isearch = if (ctx.settings.explainImplicits.value) new ExplainedImplicitSearch(pt, argument, pos) else new ImplicitSearch(pt, argument, pos) - val result = isearch.bestImplicit + val result = isearch.bestImplicit(contextual = true) result match { case result: SearchSuccess => result.tstate.commit() @@ -743,7 +743,7 @@ trait Implicits { self: Typer => def typedImplicit(cand: Candidate)(implicit ctx: Context): SearchResult = track("typedImplicit") { ctx.traceIndented(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { assert(constr eq ctx.typerState.constraint) val ref = cand.ref - var generated: Tree = tpd.ref(ref).withPos(pos.startPos) + var generated: Tree = tpd.ref(ref).withPos(pos) if (!argument.isEmpty) generated = typedUnadapted( untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), @@ -759,13 +759,20 @@ trait Implicits { self: Typer => case _ => false } } - // Does there exist an implicit value of type `Eq[tp, tp]`? - def hasEq(tp: Type): Boolean = - new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).bestImplicit match { - case result: SearchSuccess => result.ref.symbol != defn.Predef_eqAny - case result: AmbiguousImplicits => true - case _ => false - } + // Does there exist an implicit value of type `Eq[tp, tp]` + // which is different from `eqAny`? + def hasEq(tp: Type): Boolean = { + def search(contextual: Boolean): Boolean = + new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos) + .bestImplicit(contextual) match { + case result: SearchSuccess => + result.ref.symbol != defn.Predef_eqAny || + contextual && search(contextual = false) + case result: AmbiguousImplicits => true + case _ => false + } + search(contextual = true) + } def validEqAnyArgs(tp1: Type, tp2: Type) = { List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos)) @@ -872,12 +879,15 @@ trait Implicits { self: Typer => } /** Find a unique best implicit reference */ - def bestImplicit: SearchResult = { - searchImplicits(ctx.implicits.eligible(wildProto), contextual = true) match { + def bestImplicit(contextual: Boolean): SearchResult = { + val eligible = + if (contextual) ctx.implicits.eligible(wildProto) + else implicitScope(wildProto).eligible + searchImplicits(eligible, contextual) match { case result: SearchSuccess => result case result: AmbiguousImplicits => result case result: SearchFailure => - searchImplicits(implicitScope(wildProto).eligible, contextual = false) + if (contextual) bestImplicit(contextual = false) else result } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index da9eb62f88c8..948c5e8ef313 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1963,7 +1963,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _: RefTree | _: Literal if !isVarPattern(tree) && !(tree.tpe <:< pt)(ctx.addMode(Mode.GADTflexible)) => - checkCanEqual(pt, wtp, tree.pos)(ctx.retractMode(Mode.Pattern)) + val tp1 :: tp2 :: Nil = harmonizeTypes(pt :: wtp :: Nil) + checkCanEqual(tp1, tp2, tree.pos)(ctx.retractMode(Mode.Pattern)) case _ => } tree diff --git a/docs/docs/reference/auto-parameter-tupling.md b/docs/docs/reference/auto-parameter-tupling.md new file mode 100644 index 000000000000..910d13424c7e --- /dev/null +++ b/docs/docs/reference/auto-parameter-tupling.md @@ -0,0 +1,33 @@ +--- +layout: doc-page +title: "Automatic Tupling of Function Parameters" +--- + +Say you have a list of pairs + + val xs: List[(Int, Int)] + +and you want to map `xs` to a list of `Int`s so that eich pair of numbers is mapped to +their sum. Previously, the best way to do this was with a pattern-matching decomposition: + + xs map { + case (x, y) => x + y + } + +While correct, this is also inconvenient. Dotty now also allows: + + xs.map { + (x, y) => x + y + } + +or, equivalently: + + xs.map(_ + _) + +Generally, a function value with `n > 1` parameters is converted to a +pattern-matching closure using `case` if the expected type is a unary +function type of the form `((T_1, ..., T_n)) => U`. + + + + diff --git a/docs/docs/reference/desugarEnums.md b/docs/docs/reference/desugarEnums.md index c07d08d37d56..d3c9208c47a8 100644 --- a/docs/docs/reference/desugarEnums.md +++ b/docs/docs/reference/desugarEnums.md @@ -89,7 +89,7 @@ comma separated simple cases into a sequence of cases. case C ... - expands analogous to a case class: + expands analogous to a final case class: final case class C ... @@ -138,6 +138,15 @@ comma separated simple cases into a sequence of cases. Any modifiers or annotations on the original case extend to all expanded cases. +## Equality + +An `enum` type contains a `scala.Eq` instance that restricts values of the `enum` type to +be compared only to other values of the same enum type. Furtermore, generic +`enum` types are comparable only if their type arguments are. For instance the +`Option` enum type will get the following definition in its companion object: + + implicit def eqOption[T, U](implicit ev1: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + ## Translation of Enumerations Non-generic enum classes `E` that define one or more singleton cases diff --git a/docs/docs/reference/implicit-function-types.md b/docs/docs/reference/implicit-function-types.md new file mode 100644 index 000000000000..0f9b9b42c672 --- /dev/null +++ b/docs/docs/reference/implicit-function-types.md @@ -0,0 +1,99 @@ +--- +layout: doc-page +title: "Implicit Function Types" +--- + +An implicit funciton type describes functions with implicit parameters. Example: + + type Contextual[T] = implicit Context => T + +A value of implicit function type is applied to implicit arguments, in +the same way a method with implicit parameters is applied. For instance: + + implicit ctx: Context = ... + + def f(x: Int): Contextual[Int] = ... + + f(2) // is expanded to f(2)(ctx) + +Conversely, if the expected type of an expression `E` is an implicit +function type `implicit (T_1, ..., T_n) => U` and `E` is not already an +implicit function value, `E` is converted to an implicit function value +by rewriting to + + implicit (x_1: T1, ..., x_n: Tn) => E + +where the names `x_1`, ..., `x_n` are arbitrary. For example, continuing +with the previous definitions, + + def g(arg: Contextual[Int]) = ... + + g(22) // is expanded to g { implicit ctx => 22 } + + g(f(2)) // is expanded to g { implicit ctx => f(2)(ctx) } + + g(implicit ctx => f(22)(ctx)) // is left as it is + +Implicit function types have considerable expressive power. For +instance, here is how they can support the "builder pattern", where +the aim is to construct tables like this: + + table { + row { + cell("top left") + cell("top right") + } + row { + cell("botttom left") + cell("bottom right") + } + } + +The idea is to define classes for `Table` and `Row` that allow +addition of elements via `add`: + + class Table { + val rows = new ArrayBuffer[Row] + def add(r: Row): Unit = rows += r + override def toString = rows.mkString("Table(", ", ", ")") + } + + class Row { + val cells = new ArrayBuffer[Cell] + def add(c: Cell): Unit = cells += c + override def toString = cells.mkString("Row(", ", ", ")") + } + + case class Cell(elem: String) + +Then, the `table`, `row` and `cell` constructor methods can be defined +in terms of implicit function types to avoid the plumbing boilerplate +that would otherwise be necessary. + + def table(init: implicit Table => Unit) = { + implicit val t = new Table + init + t + } + + def row(init: implicit Row => Unit)(implicit t: Table) = { + implicit val r = new Row + init + t.add(r) + } + + def cell(str: String)(implicit r: Row) = + r.add(new Cell(str)) + +With that setup, the table construction code above compiles and expands to: + + table { implicit $t: Table => + row { implicit $r: Row => + cell("top left")($r) + cell("top right")($r) + }($t) + row { implicit $r: Row => + cell("botttom left")($r) + cell("bottom right")($r) + }($t) + } diff --git a/docs/docs/reference/inline.md b/docs/docs/reference/inline.md new file mode 100644 index 000000000000..9515529f117e --- /dev/null +++ b/docs/docs/reference/inline.md @@ -0,0 +1,130 @@ +--- +layout: doc-page +title: Inline +--- + +`inline` is a new modifier that guarantees that a definition will be +inlined at the point of use. Example: + + object Config { + inline val logging = false + } + + object Logger { + + private var indent = 0 + + inline def log[T](msg: => String)(op: => T): T = + if (Config.logging) { + println(s"${" " * indent}start $msg") + indent += 1 + val result = op + indent -= 1 + println(s"${" " * indent}$msg = $result") + result + } + else op + } + +The `Config` object contains a definition of an `inline` value +`logging`. This means that `logging` is treated as a constant value, +equivalent to its right-hand side `false`. The right-hand side of such +an inline val must itself be a constant expression. Used in this way, +`inline` is equivalent to Java and Scala 2's `final`. `final` meaning +"constant" is still supported in Dotty, but will be phased out. + +The `Logger` object contains a definition of an `inline` method `log`. +This method will always be inlined at the point of call. + +In the inlined code, an if-then-else with a constant condition will be +rewritten to its then- or else-part. Here's an example: + + def factorial(n: BigInt): BigInt = + log(s"factorial($n)") { + if (n == 0) 1 + else n * factorial(n - 1) + } + +If `Config.logging == false`, this will be rewritten to + + def factorial(n: BigInt): BigInt = { + def msg = s"factorial($n)" + def op = + if (n == 0) 1 + else n * factorial(n - 1) + op + } + +Note that the arguments corresponding to the parameters `msg` and `op` +of the inline method `log` are defined before the inlined body (which +is in this case simply `op`). By-name parameters of the inlined method +correspond to `def` bindings whereas by-value parameters correspond to +`val` bindings. Do if `log` was defined like this: + + inline def log[T](msg: String)(op: => T): T = ... + +we'd get + + val msg = s"factorial($n)" + +instead. This behavior is designed so that calling an inline method is +semantically the same as calling a normal method: By-value arguments +are evaluated before the call wherea by-name arguments are evaluated +each time they are referenced. As a consequence, it is often +preferrable to make arguments of inline methods by-name in order to +avoid unnecessary evaluations. + +Inline methods can be recursive. For instance, when called with a constant +exponent `n`, the following method for `power` will be implemented by +straight inline code without any loop or recursion. + + inline def power(x: Double, n: Int): Double = + if (n == 0) 1.0 + else if (n == 1) x + else { + val y = power(x, n / 2) + if (n % 2 == 0) y * y else y * y * x + } + + power(expr, 10) + // translates to + // + // val x = expr + // val y1 = x * x // ^2 + // val y2 = y1 * y1 // ^4 + // val y3 = y2 * x // ^5 + // y3 * y3 // ^10 + +Parameters of inline methods can themselves be marked `inline`. This means +that the argument of an inline method is itself inlined in the inlined body of +the method. Using this scheme, we can define a zero-overhead `foreach` method +that translates into a straightforward while loop without any indirection or +overhead: + + inline def foreach(inline op: Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + +## Relationship to `@inline`. + +Existing Scala defines a `@inline` annotation which is used as a hint +for the backend to inline. For most purposes, this annotation is +superseded by the `inline` modifier. The modifier is more powerful +than the annotation: Expansion is guarenteed instead of best effort, +it happens in the fronend instead of in the backend, and it also applies +to method arguments and recursive methods. + +Since `inline` is now a keyword, it would be a syntax error to write +`@inline`. Hwoever, one can still refer to the annotation by putting +it in backticks, i.e. + + @`inline` def ... + + + + + diff --git a/docs/docs/reference/multiversal-equality.md b/docs/docs/reference/multiversal-equality.md new file mode 100644 index 000000000000..5f2cdbca9752 --- /dev/null +++ b/docs/docs/reference/multiversal-equality.md @@ -0,0 +1,97 @@ +--- +layout: doc-page +title: "Multiversal Equality" +--- + +Previously, Scala had universal equality: Two values of any types +could be compared with each other with `==` and `!=`. This came from +the fact that `==` and `!=` are implemented in terms of Java's +`equals` method, which can also compare values of any two reference +types. + +Universal equality is convenient but also dangerous since it +undermines type safety. Say you have an erroneous program where +a value `y` has type `S` instead of the expected type `T`. + + val x = ... // of type T + val y = ... // of type S, but should be T + x == y // typechecks, will always yield false + +If all you do with `y` is compare it to other values of type `T`, the program will +typecheck but probably give unexpected results. + +Multiversal equality is an opt-in way to make universal equality +safer. The idea is that by declaring an `implicit` value one can +restrict the types that are legal in comparisons. The example above +would not typecheck if an implicit was declared like this for type `T` +(or an analogous one for type `S`): + + implicit def eqT: Eq[T, T] = Eq + +This definition effectively says that value of type `T` can (only) be +compared with `==` or `!=` to other values of type `T`. The definition +is used only for type checking; it has no significance for runtime +behavior, since `==` always maps to `equals` and `!=` alwatys maps to +the negation of `equals`. The right hand side of the definition is a value +that has any `Eq` instance as its type. Here is the definition of class +`Eq` and its companion object: + + package scala + import annotation.implicitNotFound + + @implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") + sealed trait Eq[-L, -R] + + object Eq extends Eq[Any, Any] + +One can have several `Eq` instances for a type. For example, the four +definitions below make values of type `A` and type `B` comparable with +each other, but not comparable to anything else: + + implicit def eqA : Eq[A, A] = Eq + implicit def eqB : Eq[B, B] = Eq + implicit def eqAB: Eq[A, B] = Eq + implicit def eqBA: Eq[B, A] = Eq + +(As usual, the names of the implicit definitions don't matter, we have +chosen `eqA`, ..., `eqBA` only for illustration). + +The `dotty.DottyPredef` object defines a number of `Eq` +implicits. `dotty.DottyPredef` is a temporary `Predef`-like object. +The contents of this object are by default imported into every +program. Once dotty becomes standard Scala, `DottyPredef` will go away +and its contents will be merged with `scala.Predef`. + +The `Eq` instances defined by `DottyPredef` make values of types +`String`, `Boolean` and `Unit` only comparable to values of the same +type. They also make numbers only comparable to other numbers, and +sequences only comparable to other sequences. There's also a +"fallback" instance `eqAny` that allows comparisons over types that do +not themeselves have an `Eq` instance. `eqAny` is defined as follows: + + implicit def eqAny[L, R]: Eq[L, R] = Eq + +The primary motivation for having `eqAny` is backwards compatibility, +if this is of no concern one can disable `eqAny` by unimporting it +from `DottyPredef` like this + + import dotty.DottyPredef.{eqAny => _, _} + +All `enum` types also come with `Eq` instances that make values of the +`enum` type comparable only to other values of that `enum` type. + +The precise rules for equality checking are as follows. + + 1. A comparison using `x == y` or `x != y` between values `x: T` and `y: U` + is legal if either `T` and `U` are the same, or one of the types is a subtype + of the other, or an implicit value of type `scala.Eq[T, U]` is found. + + 2. The usual rules for implicit search apply also to `Eq` instances, + with one modification: The value `eqAny` in `dotty.DottyPredef` is + eligible only if neither `T` nor `U` have a reflexive `Eq` + instance themselves. Here, a type `T` has a reflexive `Eq` + instance if the implicit search for `Eq[T, T]` where `eqAny` is + not eligible is successful. + +More on multiversal equality is found in a [blog post] +and a [Github issue]. \ No newline at end of file diff --git a/docs/docs/reference/named-typeargs.md b/docs/docs/reference/named-typeargs.md new file mode 100644 index 000000000000..60973b7bdc83 --- /dev/null +++ b/docs/docs/reference/named-typeargs.md @@ -0,0 +1,25 @@ +--- +layout: doc-page +title: "Named Type Arguments" +--- + +Type arguments of methods can now be named as well as by position. Example: + + + def construct[Elem, Coll[_]](xs: Elem*): Coll[Elem] = ??? + + val xs2 = construct[Coll = List, Elem = Int](1, 2, 3) + val xs3 = construct[Coll = List](1, 2, 3) + +Similar to a named value argument `(x = e)`, a named type argument +`[X = T]` instantiates the type parameter `X` to the type `T`. Type +arguments must be all named or un-named, mixtures of named and +positional type arguments are not supported. + +The main benefit of named type arguments is that they allow some +arguments to be omitted. Indeed, if type arguments are named, some +arguments may be left out. An example is the definition of `xs3` +above. A missing type argument is inferred as usual by local type +inference. The same is not true for positional arguments, which have +to be given always for all type parameters. + diff --git a/docs/docs/reference/type-lambdas.md b/docs/docs/reference/type-lambdas.md new file mode 100644 index 000000000000..748033840aa2 --- /dev/null +++ b/docs/docs/reference/type-lambdas.md @@ -0,0 +1,22 @@ +--- +layout: doc-page +title: "Type Lambdas" +--- + +A _type lambda_ lets one express a higher-kinded type directly, without +a type definition. + + [+X, Y] => Map[Y, X] + +For instance, the type above defines a binary type constructor, with a +covariant parameter `X` and a non-variant parameter `Y`. The +constructor maps arguments `S` and `T` to `Map[T, S]`. Type parameters +of type lambdas can have variances and bounds. A parameterized type +definition or declaration such as + + type T[X] = (X, X) + +is a shorthand for a plain type definition with a type-lambda as its +right-hand side: + + type T = [X] => (X, X) diff --git a/docs/sidebar.yml b/docs/sidebar.yml index d61769b93711..3d185c252cb8 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -3,10 +3,14 @@ sidebar: url: blog/index.html - title: Reference subsection: + - title: Implicit Function Types + url: docs/reference/implicit-function-types.md - title: Intersection types url: docs/reference/intersection-types.html - title: Union types url: docs/reference/union-types.html + - title: Type lambdas + url: docs/reference/type-lambdas.html - title: Trait Parameters url: docs/reference/trait-parameters.html - title: Enumerations @@ -15,8 +19,16 @@ sidebar: url: docs/reference/adts.html - title: Enum Translation url: docs/reference/desugarEnums.html + -title: Multiversal Equality + url: docs/reference/multiversal-equality.html - title: By-Name Implicits - url: docs/reference/implicit-by-name-parameters.html + url: docs/reference/implicit-by-name-parameters.htmldocs/reference/implicit-by-name-parameters.html + - title: Auto Parameter Tupling + url: docs/reference/auto-parameter-tupling.html + - title: Named Type Arguments + url: docs/reference/named-typeargs.html + - title: Inline + url: docs/reference/inline.html - title: Usage subsection: - title: sbt-projects diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index e78fa9239865..bb6989df9953 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -16,6 +16,15 @@ object DottyPredef { implicit def eqNumber : Eq[Number, Number] = Eq implicit def eqString : Eq[String, String] = Eq + implicit def eqBoolean : Eq[Boolean, Boolean] = Eq + implicit def eqByte : Eq[Byte, Byte] = Eq + implicit def eqShort : Eq[Short, Short] = Eq + implicit def eqChar : Eq[Char, Char] = Eq + implicit def eqInt : Eq[Int, Int] = Eq + implicit def eqLong : Eq[Long, Long] = Eq + implicit def eqFloat : Eq[Float, Float] = Eq + implicit def eqDouble : Eq[Double, Double] = Eq + implicit def eqUnit : Eq[Unit, Unit] = Eq // true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies implicit def eqProxy : Eq[Proxy, Any] = Eq diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index d48d90078f96..a7c641d11d12 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -1,3 +1,5 @@ +package enums + enum List[+T] { case Cons[T](x: T, xs: List[T]) // ok case Snoc[U](xs: List[U], x: U) // error: different type parameters @@ -18,3 +20,18 @@ enum E2[+T, +U >: T] { enum E3[-T <: Ordered[T]] { case C // error: cannot determine type argument } + +enum Option[+T] { + case Some[T](x: T) + case None +} + +object Test { + + class Unrelated + + val x: Option[Int] = Option.Some(1) + x == new Unrelated // error: cannot compare + +} + diff --git a/tests/neg/equality1.scala b/tests/neg/equality1.scala new file mode 100644 index 000000000000..e9bc2cbaaf1a --- /dev/null +++ b/tests/neg/equality1.scala @@ -0,0 +1,7 @@ +import dotty.DottyPredef.{eqAny => _, _} + +object equality1 { + class A + class B + new A == new B // error: cannot compare +} diff --git a/tests/neg/equality2.scala b/tests/neg/equality2.scala new file mode 100644 index 000000000000..0870be114f3d --- /dev/null +++ b/tests/neg/equality2.scala @@ -0,0 +1,12 @@ +object equality2 { + class A + + val x: Int = 3 + x == new A // error: cannot compare + new A == x // error: cannot compare + 1 == new A // error: cannot compare + new A == 1 // error: cannot compare + + true == new A // error: cannot compare + new A == true // error: cannot compare +} diff --git a/tests/neg/tryPatternMatchEq.scala b/tests/neg/tryPatternMatchEq.scala new file mode 100644 index 000000000000..d29924d63dd9 --- /dev/null +++ b/tests/neg/tryPatternMatchEq.scala @@ -0,0 +1,34 @@ +import java.io.IOException +import java.lang.NullPointerException +import java.lang.IllegalArgumentException + +object IAE { + def unapply(e: Exception): Option[String] = + if (e.isInstanceOf[IllegalArgumentException]) Some(e.getMessage) + else None +} + +object EX extends Exception + +trait ExceptionTrait extends Exception + +object Test { + def main(args: Array[String]): Unit = { + var a: Int = 1 + try { + throw new IllegalArgumentException() + } catch { + case e: IOException if e.getMessage == null => + case e: NullPointerException => + case e: IndexOutOfBoundsException => + case _: NoSuchElementException => + case _: ExceptionTrait => + case _: NoSuchElementException if a <= 1 => + case _: NullPointerException | _:IOException => + case `a` => // error: cannot compare + case EX => + case IAE(msg) => + case e: IllegalArgumentException => + } + } +} diff --git a/tests/neg/tryPatternMatchError.scala b/tests/neg/tryPatternMatchError.scala index fe12a62329be..b4564c758d8c 100644 --- a/tests/neg/tryPatternMatchError.scala +++ b/tests/neg/tryPatternMatchError.scala @@ -25,7 +25,6 @@ object Test { case _: ExceptionTrait => case _: NoSuchElementException if a <= 1 => case _: NullPointerException | _:IOException => - case `a` => // This case should probably emmit an error case e: Int => // error case EX => case IAE(msg) => diff --git a/tests/pos/Meter.scala b/tests/pos/Meter.scala index bbee710c4f14..c32d6a4142f1 100644 --- a/tests/pos/Meter.scala +++ b/tests/pos/Meter.scala @@ -69,7 +69,7 @@ object Test extends App { println("x.hashCode: " + x.hashCode) - println("x == 1: " +(x == 1)) + println("x == 1: " +((x: Any) == 1)) println("x == y: " +(x == y)) assert(x.hashCode == (1.0).hashCode) diff --git a/tests/pos/i1665.scala b/tests/pos/i1665.scala index b7e4a4eedd38..371322126f03 100644 --- a/tests/pos/i1665.scala +++ b/tests/pos/i1665.scala @@ -2,6 +2,6 @@ object Test { !=(1) !=("abc") - 1 != this + 1 != (this: Any) !=(this) } diff --git a/tests/pos/reference/auto-param-tupling.scala b/tests/pos/reference/auto-param-tupling.scala new file mode 100644 index 000000000000..e700b019eb41 --- /dev/null +++ b/tests/pos/reference/auto-param-tupling.scala @@ -0,0 +1,17 @@ +package autoParamTupling { + +object t1 { + val xs: List[(Int, Int)] = ??? + + xs.map { + case (x, y) => x + y + } + + xs.map { + (x, y) => x + y + } + + xs.map(_ + _) + +} +} diff --git a/tests/pos/reference/inlines.scala b/tests/pos/reference/inlines.scala new file mode 100644 index 000000000000..c83ca72d3cbf --- /dev/null +++ b/tests/pos/reference/inlines.scala @@ -0,0 +1,34 @@ +package inlines + +object Config { + inline val logging = false +} + +object Logger { + + private var indent = 0 + + inline def log[T](msg: String)(op: => T): T = + if (Config.logging) { + println(s"${" " * indent}start $msg") + indent += 1 + val result = op + indent -= 1 + println(s"${" " * indent}$msg = $result") + result + } + else op +} + +object Test { + import Logger._ + + def factorial(n: BigInt): BigInt = + log(s"factorial($n)") { + if (n == 0) 1 + else n * factorial(n - 1) + } + + def main(args: Array[String]): Unit = + println(factorial(33)) +} diff --git a/tests/pos/reference/named-typeargs.scala b/tests/pos/reference/named-typeargs.scala new file mode 100644 index 000000000000..a909734939ff --- /dev/null +++ b/tests/pos/reference/named-typeargs.scala @@ -0,0 +1,13 @@ +package namedTypeArgsR { + +object t1 { + + def construct[Elem, Coll[_]](xs: Elem*): Coll[Elem] = ??? + + val xs3 = construct[Coll = List](1, 2, 3) + + val xs2 = construct[Coll = List, Elem = Int](1, 2, 3) + +} + +} diff --git a/tests/pos/reference/type-lambdas.scala b/tests/pos/reference/type-lambdas.scala new file mode 100644 index 000000000000..af5f84709f95 --- /dev/null +++ b/tests/pos/reference/type-lambdas.scala @@ -0,0 +1,7 @@ +package typeLambdas + +object Test { + + type T = [+X, Y] => Map[Y, X] + +} diff --git a/tests/run/enum-Option.scala b/tests/run/enum-Option.scala index 6bdb2b1eb262..364c35fa0b6d 100644 --- a/tests/run/enum-Option.scala +++ b/tests/run/enum-Option.scala @@ -16,5 +16,6 @@ object Test { def main(args: Array[String]) = { assert(Some(None).isDefined) Option("22") match { case Option.Some(x) => assert(x == "22") } + assert(Some(None) != None) } } diff --git a/tests/run/t3529.scala b/tests/run/t3529.scala index a5977d0a6c55..eae568214461 100644 --- a/tests/run/t3529.scala +++ b/tests/run/t3529.scala @@ -8,7 +8,8 @@ object Test { assert((10 to 1 by -1 drop 9) == Seq(1)) assert((1 to 10 drop 9) == Seq(10)) - assert((1 until 10 drop 9) == Nil) + assert((1 until 10 drop 9) == (Nil: List[Int])) + // investigate why we need the type annotation here. See #2567. assert(Stream(1 to 10).flatten.toList == Stream(1 until 11).flatten.toList) } diff --git a/tests/run/tryPatternMatch.scala b/tests/run/tryPatternMatch.scala index 06b469d4d33d..db01ec172288 100644 --- a/tests/run/tryPatternMatch.scala +++ b/tests/run/tryPatternMatch.scala @@ -112,7 +112,7 @@ object Test extends TestTrait { case _: ExceptionTrait => println("ExceptionTrait") case e: IOException if e.getMessage == null => println("IOException") case _: NullPointerException | _:IOException => println("NullPointerException | IOException") - case `a` => println("`a`") +// case `a` => println("`a`") case EX => println("EX") case e: IllegalArgumentException => println("IllegalArgumentException") case _: ClassCastException => println("ClassCastException")