From fb7084c4a49b54ccfe8b28c48f7f6e03928de8e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 14:38:39 +0100 Subject: [PATCH 01/54] Add opaque types: parsing & pickling Add `opaque` to syntax. Let it be parsed and stored/pickled as a flag. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 ++ compiler/src/dotty/tools/dotc/core/Flags.scala | 13 ++++++++----- .../dotty/tools/dotc/core/tasty/TastyFormat.scala | 6 +++++- .../dotty/tools/dotc/core/tasty/TreePickler.scala | 1 + .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Tokens.scala | 5 +++-- docs/docs/internals/syntax.md | 1 + 8 files changed, 22 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index f10670c42345..3f2136b8ce17 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -124,6 +124,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Sealed() extends Mod(Flags.Sealed) + case class Opaque() extends Mod(Flags.Opaque) + case class Override() extends Mod(Flags.Override) case class Abstract() extends Mod(Flags.Abstract) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 9f88fdbc0cf3..ed702f328558 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -251,9 +251,12 @@ object Flags { final val AccessorOrSealed = Accessor.toCommonFlags - /** A mutable var */ + /** A mutable var */ final val Mutable = termFlag(12, "mutable") + /** An opqaue type */ + final val Opaque = typeFlag(12, "opaque") + /** Symbol is local to current class (i.e. private[this] or protected[this] * pre: Private or Protected are also set */ @@ -264,7 +267,7 @@ object Flags { */ final val ParamAccessor = termFlag(14, "") - /** A value or class implementing a module */ + /** A value or class implementing a module */ final val Module = commonFlag(15, "module") final val ModuleVal = Module.toTermFlags final val ModuleClass = Module.toTypeFlags @@ -441,12 +444,12 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) + Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased, Opaque) /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local | - commonFlags(Mutable) + SourceModifierFlags | Module | Param | Synthetic | Package | Local + // | Mutable is subsumed by commonFlags(Opaque) from SourceModifierFlags // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index f082765d6cb7..1d45123d1ebb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -183,6 +183,7 @@ Standard-Section: "ASTs" TopLevelStat* OVERRIDE INLINE // inline method MACRO // inline method containing toplevel splices + OPAQUE // opaque type STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait @@ -302,7 +303,8 @@ object TastyFormat { final val STABLE = 31 final val MACRO = 32 final val ERASED = 33 - final val PARAMsetter = 34 + final val OPAQUE = 34 + final val PARAMsetter = 35 // Cat. 2: tag Nat @@ -450,6 +452,7 @@ object TastyFormat { | OVERRIDE | INLINE | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -506,6 +509,7 @@ object TastyFormat { case OVERRIDE => "OVERRIDE" case INLINE => "INLINE" case MACRO => "MACRO" + case OPAQUE => "OPAQUE" case STATIC => "STATIC" case OBJECT => "OBJECT" case TRAIT => "TRAIT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f7d942b4bdad..22d1a6eb3236 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -608,6 +608,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Trait) writeByte(TRAIT) if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) + if (flags is Opaque) writeByte(OPAQUE) } sym.annotations.foreach(pickleAnnotation(sym, _)) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index ddf3ef7ed765..b0df33c4fe8d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -574,6 +574,7 @@ class TreeUnpickler(reader: TastyReader, case OVERRIDE => addFlag(Override) case INLINE => addFlag(Inline) case MACRO => addFlag(Macro) + case OPAQUE => addFlag(Opaque) case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) case TRAIT => addFlag(Trait) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9f86e7bde967..ce7e56b1cd31 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1649,6 +1649,7 @@ object Parsers { case PRIVATE => Mod.Private() case PROTECTED => Mod.Protected() case SEALED => Mod.Sealed() + case OPAQUE => Mod.Opaque() } /** Drop `private' modifier when followed by a qualifier. diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 0cc0cc16fcea..5becc9161309 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -178,6 +178,7 @@ object Tokens extends TokensCommon { final val INLINE = 62; enter(INLINE, "inline") final val ENUM = 63; enter(ENUM, "enum") final val ERASED = 64; enter(ERASED, "erased") + final val OPAQUE = 65; enter(OPAQUE, "opaque") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -198,7 +199,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, ERASED) + final val alphaKeywords = tokenRange(IF, OPAQUE) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -226,7 +227,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED) + ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED, OPAQUE) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index a377abee83f8..3c9d9ef1c6c7 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -286,6 +286,7 @@ LocalModifier ::= ‘abstract’ | ‘sealed’ | ‘implicit’ | ‘lazy’ + | ‘opaque’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ (id | ‘this’) ‘]’ From b3193a507cbd89d12c11f8d1d91100179fef366e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 15:54:27 +0100 Subject: [PATCH 02/54] Store opaque info in annotation An opaque type becomes an abstract type, with the alias stored in an OpaqueAlias annotation --- compiler/src/dotty/tools/dotc/core/Annotations.scala | 12 ++++++++++++ compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 ++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 8 ++++++++ .../dotty/tools/dotc/core/tasty/TreePickler.scala | 4 ++-- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 1 + .../src/dotty/tools/dotc/transform/SymUtils.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 1 + compiler/src/dotty/tools/dotc/typer/Namer.scala | 1 + .../src/scala/annotation/internal/OpaqueAlias.scala | 11 +++++++++++ 9 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 library/src/scala/annotation/internal/OpaqueAlias.scala diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 2da8c95f0b71..0021e414a22b 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -173,6 +173,18 @@ object Annotations { else None } + /** Extractor for opaque alias annotations */ + object OpaqueAlias { + def apply(tp: Type)(implicit ctx: Context): Annotation = + Annotation(TypeTree(defn.OpaqueAliasAnnotType.appliedTo(tp))) + def unapply(ann: Annotation)(implicit ctx: Context) = + if (ann.symbol == defn.OpaqueAliasAnnot) { + val AppliedType(tycon, arg :: Nil) = ann.tree.tpe + Some(arg) + } + else None + } + def makeSourceFile(path: String)(implicit ctx: Context) = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 95c5aa94dd37..802f8c9fedcc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -683,6 +683,8 @@ class Definitions { def BodyAnnot(implicit ctx: Context) = BodyAnnotType.symbol.asClass lazy val ChildAnnotType = ctx.requiredClassRef("scala.annotation.internal.Child") def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass + lazy val OpaqueAliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.OpaqueAlias") + def OpaqueAliasAnnot(implicit ctx: Context) = OpaqueAliasAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index c190dfb7262c..a98122bb166f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -380,6 +380,14 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } + final def normalizeOpaque()(implicit ctx: Context) = { + if (is(Opaque)) { + addAnnotation(Annotation.OpaqueAlias(info)) + setFlag(Deferred) + info = TypeBounds.empty + } + } + // ------ Names ---------------------------------------------- /** The expanded name of this denotation. */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 22d1a6eb3236..88560b46b9ef 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -619,8 +619,8 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot - // inline bodies are reconstituted automatically when unpickling + case _ => ann.symbol == defn.BodyAnnot || ann.symbol == defn.OpaqueAliasAnnot + // inline bodies and opaque aliases are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b0df33c4fe8d..f4e0971576b7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -539,6 +539,7 @@ class TreeUnpickler(reader: TastyReader, // avoids space leaks by not capturing the current context forkAt(rhsStart).readTerm() }) + sym.normalizeOpaque() goto(start) sym } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index d8a98309a078..880d7f686830 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -126,9 +126,7 @@ class SymUtils(val self: Symbol) extends AnyVal { def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { if (!self.unforcedDecls.lookup(name).exists) { val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) - if (companionMethod.exists) { - companionMethod.entered - } + if (companionMethod.exists) companionMethod.entered } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 698fdaff0c15..6502d9b4bcbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -381,6 +381,7 @@ object Checking { checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`") if (sym.is(Inline)) checkApplicable(Inline, sym.isTerm && !sym.is(Mutable | Module)) if (sym.is(Lazy)) checkApplicable(Lazy, !sym.is(Method | Mutable)) + if (sym.is(Opaque)) checkApplicable(Opaque, sym.isAliasType) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 69836719c7d3..a45e6a66d26b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -842,6 +842,7 @@ class Namer { typer: Typer => addInlineInfo(denot) denot.info = typeSig(sym) Checking.checkWellFormed(sym) + denot.normalizeOpaque() denot.info = avoidPrivateLeaks(sym, sym.pos) } } diff --git a/library/src/scala/annotation/internal/OpaqueAlias.scala b/library/src/scala/annotation/internal/OpaqueAlias.scala new file mode 100644 index 000000000000..a6aaa54c6b66 --- /dev/null +++ b/library/src/scala/annotation/internal/OpaqueAlias.scala @@ -0,0 +1,11 @@ +package scala.annotation.internal + +import scala.annotation.Annotation + +/** An annotation to record the right-hand side of an opaque type. Given + * + * opaque type T = U + * + * the info of `T` is `Nothing..Any`, but `T` carries the annotation `OpaqueAlias[U]` + */ +class OpaqueAlias[T] extends Annotation From e58333892c9e5190ac7947135850f09b3e1e50c5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:41:22 +0100 Subject: [PATCH 03/54] Keep track of opaque companion links maintain the link from a module class to its opaque type companion, using the same technique as for companion classes. --- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 35 ++++++++++++------- .../tools/dotc/core/tasty/TreeUnpickler.scala | 10 ++++-- .../src/dotty/tools/dotc/typer/Namer.scala | 22 ++++++------ 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a6543572b23f..d23c4c16098f 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -145,6 +145,7 @@ object StdNames { val INITIALIZER_PREFIX: N = "initial$" val COMPANION_MODULE_METHOD: N = "companion$module" val COMPANION_CLASS_METHOD: N = "companion$class" + val COMPANION_TYPE_METHOD: N = "companion$type" val BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a98122bb166f..230cbf329f8c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -382,7 +382,7 @@ object SymDenotations { final def normalizeOpaque()(implicit ctx: Context) = { if (is(Opaque)) { - addAnnotation(Annotation.OpaqueAlias(info)) + addAnnotation(Annotation.OpaqueAlias(info.bounds.lo)) setFlag(Deferred) info = TypeBounds.empty } @@ -494,14 +494,15 @@ object SymDenotations { final def isAnonymousModuleVal(implicit ctx: Context) = this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) - /** Is this a companion class method or companion object method? + /** Is this a companion class or type method or companion object method? * These methods are generated by Symbols#synthesizeCompanionMethod * and used in SymDenotations#companionClass and * SymDenotations#companionModule . */ final def isCompanionMethod(implicit ctx: Context) = name.toTermName == nme.COMPANION_CLASS_METHOD || - name.toTermName == nme.COMPANION_MODULE_METHOD + name.toTermName == nme.COMPANION_MODULE_METHOD || + name.toTermName == nme.COMPANION_TYPE_METHOD /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods @@ -967,20 +968,28 @@ object SymDenotations { } } - /** The class with the same (type-) name as this module or module class, - * and which is also defined in the same scope and compilation unit. - * NoSymbol if this class does not exist. - */ - final def companionClass(implicit ctx: Context): Symbol = + private def companionType(name: TermName)(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol else { - val companionMethod = info.decls.denotsNamed(nme.COMPANION_CLASS_METHOD, selectPrivate).first - if (companionMethod.exists) - companionMethod.info.resultType.classSymbol - else - NoSymbol + val companionMethod = info.decls.denotsNamed(name, selectPrivate).first + if (companionMethod.exists) companionMethod.info.resultType.typeSymbol + else NoSymbol } + /** The class with the same (type-) name as this module or module class, + * and which is also defined in the same scope and compilation unit. + * NoSymbol if this class does not exist. + */ + final def companionClass(implicit ctx: Context): Symbol = + companionType(nme.COMPANION_CLASS_METHOD).suchThat(_.isClass).symbol + + /** The opaque type with the same (type-) name as this module or module class, + * and which is also defined in the same scope and compilation unit. + * NoSymbol if this type does not exist. + */ + final def companionOpaqueType(implicit ctx: Context): Symbol = + companionType(nme.COMPANION_TYPE_METHOD).suchThat(_.is(Opaque)).symbol + final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) else if (this.isClass) companionNamed(effectiveName.moduleClassName).sourceModule.moduleClass diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f4e0971576b7..5a31036991f5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -752,8 +752,14 @@ class TreeUnpickler(reader: TastyReader, def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) if (companion.exists && isCodefined) { - if (sym is Flags.ModuleClass) sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) - else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) + if (sym is Module) { + if (companion.isClass) + sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) + else if (companion.is(Opaque)) + sym.registerCompanionMethod(nme.COMPANION_TYPE_METHOD, companion) + } + else + sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) } TypeDef(readTemplate(localCtx)) } else { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a45e6a66d26b..ea0abbafe7a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -613,22 +613,24 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (claz.isClass && modl.isClass) { - ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered - ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered - } + if (modl.isClass) + if (claz.isClass) { + ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered + ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered + } + else if (claz.is(Opaque)) + ctx.synthesizeCompanionMethod(nme.COMPANION_TYPE_METHOD, claz, modl).entered } def createCompanionLinks(implicit ctx: Context): Unit = { val classDef = mutable.Map[TypeName, TypeDef]() val moduleDef = mutable.Map[TypeName, TypeDef]() - def updateCache(cdef: TypeDef): Unit = { - if (!cdef.isClassDef || cdef.mods.is(Package)) return - - if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef - else classDef(cdef.name) = cdef - } + def updateCache(cdef: TypeDef): Unit = + if (cdef.isClassDef && !cdef.mods.is(Package) || cdef.mods.is(Opaque)) { + if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef + else classDef(cdef.name) = cdef + } for (stat <- stats) expanded(stat) match { From 3e53f63cf11bfb63aeade9029242aee26e93496a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:43:52 +0100 Subject: [PATCH 04/54] Maintain companion aliases as GADT bounds --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ea0abbafe7a7..5a0ea210ba5d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -103,11 +103,20 @@ trait NamerContextOps { this: Context => /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - val localCtx: Context = ctx.fresh.setNewScope + var localCtx: FreshContext = ctx.fresh.setNewScope selfInfo match { case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => localCtx.scope.openForMutations.enter(sym) case _ => } + if (ctx.owner.is(Module)) { + val opaq = ctx.owner.companionOpaqueType + opaq.getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(Annotation.OpaqueAlias(rhs)) => + localCtx = localCtx.setFreshGADTBounds + localCtx.gadt.setBounds(opaq, TypeAlias(rhs)) + case _ => + } + } localCtx } @@ -506,7 +515,6 @@ class Namer { typer: Typer => case _ => } - def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { case t: MemberDef if t.rawComment.isDefined => ctx.docCtx.foreach(_.addDocstring(sym, t.rawComment)) From d0eec141055f584e97c4f8304138495c9c4b1a86 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:44:14 +0100 Subject: [PATCH 05/54] Test cases --- tests/neg/opaque.scala | 19 +++++++++++++++++++ tests/pos/opaque.scala | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/neg/opaque.scala create mode 100644 tests/pos/opaque.scala diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala new file mode 100644 index 000000000000..f1117deffef2 --- /dev/null +++ b/tests/neg/opaque.scala @@ -0,0 +1,19 @@ +object opaquetypes { + opaque val x: Int = 1 // error + + opaque class Foo // error + + opaque type T // error + + opaque type U <: String // error + + opaque type O = String + + val s: O = "" // error + + object O { + val s: O = "" // should be OK + } + +} + diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala new file mode 100644 index 000000000000..7faba38d7faa --- /dev/null +++ b/tests/pos/opaque.scala @@ -0,0 +1,23 @@ +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(`this`) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(`this` + that) + } + } +} From 8b2117b27a7723f97107bf6be4fca5aef7c8e0b6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:16:17 +0100 Subject: [PATCH 06/54] Allow for higher-kinded opaque types --- .../dotty/tools/dotc/core/SymDenotations.scala | 17 ++++++++++++++--- tests/pos/opaque.scala | 9 +++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 230cbf329f8c..f4f2b43a4ee4 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -380,11 +380,22 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } + /** If this is an opaque type alias, mark it as Deferred with empty bounds + * while storing the former right-hand side in an OpaqueAlias annotation. + */ final def normalizeOpaque()(implicit ctx: Context) = { + def abstractRHS(tp: Type): Type = tp match { + case tp: HKTypeLambda => tp.derivedLambdaType(resType = abstractRHS(tp.resType)) + case _ => defn.AnyType + } if (is(Opaque)) { - addAnnotation(Annotation.OpaqueAlias(info.bounds.lo)) - setFlag(Deferred) - info = TypeBounds.empty + info match { + case tp @ TypeAlias(alias) => + addAnnotation(Annotation.OpaqueAlias(alias)) + info = TypeBounds(defn.NothingType, abstractRHS(alias)) + setFlag(Deferred) + case _ => + } } } diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 7faba38d7faa..15d0d38d0ae4 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -21,3 +21,12 @@ object opaquetypes { } } } +object usesites { + import opaquetypes._ + import Logarithm.LogarithmOps // todo: drop + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l + l2 + val d = l3.toDouble + val l4: Logarithm = (1.0).asInstanceOf[Logarithm] +} From e0da82b2bdc09e565e938947bc86a556249a8d64 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:31:21 +0100 Subject: [PATCH 07/54] Handle higher-kinded comparisons involving GADTs Higher-kinded comparisons did not account for the fact that a type constructor in a higher-kinded application could have a narrowed GADT bound. --- .../dotty/tools/dotc/core/TypeComparer.scala | 18 ++++-- tests/neg/tagging.scala | 55 +++++++++++++++++++ tests/pos/tagging.scala | 55 +++++++++++++++++++ 3 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 tests/neg/tagging.scala create mode 100644 tests/pos/tagging.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e241a32e6a50..b8edb3d5d465 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -773,9 +773,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - if (tyconIsTypeRef) recur(tp1, tp2.superType) + if (followSuperType) recur(tp1, tp2.superType) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -784,12 +784,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case param2: TypeParamRef => isMatchingApply(tp1) || canConstrain(param2) && canInstantiate(param2) || - compareLower(bounds(param2), tyconIsTypeRef = false) + compareLower(bounds(param2), followSuperType = false) case tycon2: TypeRef => isMatchingApply(tp1) || { tycon2.info match { case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) + val gbounds2 = ctx.gadt.bounds(tycon2.symbol) + if (gbounds2 == null) compareLower(info2, followSuperType = true) + else compareLower(gbounds2 & info2, followSuperType = false) case info2: ClassInfo => val base = tp1.baseType(info2.cls) if (base.exists && base.ne(tp1)) @@ -821,8 +823,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } canConstrain(param1) && canInstantiate || isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow) - case tycon1: TypeRef if tycon1.symbol.isClass => - false + case tycon1: TypeRef => + !tycon1.symbol.isClass && { + val gbounds1 = ctx.gadt.bounds(tycon1.symbol) + if (gbounds1 == null) recur(tp1.superType, tp2) + else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2) + } case tycon1: TypeProxy => recur(tp1.superType, tp2) case _ => diff --git a/tests/neg/tagging.scala b/tests/neg/tagging.scala new file mode 100644 index 000000000000..72913872c36f --- /dev/null +++ b/tests/neg/tagging.scala @@ -0,0 +1,55 @@ +import scala.reflect.ClassTag +object tagging { + + // Tagged[S, T] means that S is tagged with T + opaque type Tagged[X, Y] = X + + object Tagged { + def tag[S, T](s: S): Tagged[S, T] = (s: S) + def untag[S, T](st: Tagged[S, T]): S = st + + def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs + def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst + + implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] = + ct + } + + import Tagged._ + + type @@[S, T] = Tagged[S, T] + + implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { + def untag: S = Tagged.untag(st) + } + + implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal { + def untags: F[S] = Tagged.untags(fs) + } + + implicit class TagOps[S](s: S) extends AnyVal { + def tag[T]: S @@ T = Tagged.tag(s) + } + + implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal { + def tags[T]: F[S @@ T] = Tagged.tags(fs) + } + + trait Meter + trait Foot + trait Fathom + + val x: Double @@ Meter = (1e7).tag[Meter] + val y: Double @@ Foot = (123.0).tag[Foot] + val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter] + + val o: Ordering[Double] = implicitly + val om: Ordering[Double @@ Meter] = o.tags[Meter] + om.compare(x, x) // 0 + om.compare(x, y) // error + xs.min(om) // 1.0 + xs.min(o) // error + + // uses ClassTag[Double] via 'Tagged.taggedClassTag'. + val ys = new Array[Double @@ Foot](20) +} diff --git a/tests/pos/tagging.scala b/tests/pos/tagging.scala new file mode 100644 index 000000000000..da2ba65f3ede --- /dev/null +++ b/tests/pos/tagging.scala @@ -0,0 +1,55 @@ +import scala.reflect.ClassTag +object tagging { + + // Tagged[S, T] means that S is tagged with T + opaque type Tagged[X, Y] = X + + object Tagged { + def tag[S, T](s: S): Tagged[S, T] = (s: S) + def untag[S, T](st: Tagged[S, T]): S = st + + def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs + def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst + + implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] = + ct + } + + import Tagged._ + + type @@[S, T] = Tagged[S, T] + + implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { + def untag: S = Tagged.untag(st) + } + + implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal { + def untags: F[S] = Tagged.untags(fs) + } + + implicit class TagOps[S](s: S) extends AnyVal { + def tag[T]: S @@ T = Tagged.tag(s) + } + + implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal { + def tags[T]: F[S @@ T] = Tagged.tags(fs) + } + + trait Meter + trait Foot + trait Fathom + + val x: Double @@ Meter = (1e7).tag[Meter] + val y: Double @@ Foot = (123.0).tag[Foot] + val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter] + + val o: Ordering[Double] = implicitly + val om: Ordering[Double @@ Meter] = o.tags[Meter] + om.compare(x, x) // 0 + // om.compare(x, y) // does not compile + xs.min(om) // 1.0 + // xs.min(o) // does not compile + + // uses ClassTag[Double] via 'Tagged.taggedClassTag'. + val ys = new Array[Double @@ Foot](20) +} From 224752917fbc27f83d15cb187fe86844417fd078 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 14:16:48 +0100 Subject: [PATCH 08/54] Change companion detection scheme The previous scheme, based on "magic" methods could not accommodate links from opaque types to their companion objects because there was no way to put a magic method on the opaque type. So we now use Annotations instead, which leads to some simplifications. Note: when comparing the old and new scheme I noted that the links from companion object of a value class to the value class itself broke after erasure, until they were restored in RestoreScopes. This looked unintended to me. The new scheme keeps the links unbroken throughout. --- .../dotty/tools/dotc/core/Annotations.scala | 16 ++++-- .../dotty/tools/dotc/core/Definitions.scala | 2 + .../src/dotty/tools/dotc/core/StdNames.scala | 3 - .../tools/dotc/core/SymDenotations.scala | 56 ++++++++----------- .../src/dotty/tools/dotc/core/Symbols.scala | 9 --- .../dotty/tools/dotc/core/TypeErasure.scala | 5 +- .../dotc/core/classfile/ClassfileParser.scala | 6 +- .../tools/dotc/core/tasty/TreePickler.scala | 6 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 12 ++-- .../core/unpickleScala2/Scala2Unpickler.scala | 7 +-- .../dotc/printing/DecompilerPrinter.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 6 +- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 3 +- .../tools/dotc/transform/RestoreScopes.scala | 12 ---- .../dotty/tools/dotc/transform/SymUtils.scala | 7 --- .../tools/dotc/transform/TreeChecker.scala | 5 +- .../src/dotty/tools/dotc/typer/Namer.scala | 11 ++-- 17 files changed, 63 insertions(+), 105 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 0021e414a22b..a8f46a3ba52f 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -173,18 +173,24 @@ object Annotations { else None } - /** Extractor for opaque alias annotations */ - object OpaqueAlias { + /** A generic extractor for annotations carrying types */ + class TypeHintExtractor(cls: Context => ClassSymbol) { def apply(tp: Type)(implicit ctx: Context): Annotation = - Annotation(TypeTree(defn.OpaqueAliasAnnotType.appliedTo(tp))) - def unapply(ann: Annotation)(implicit ctx: Context) = - if (ann.symbol == defn.OpaqueAliasAnnot) { + Annotation(TypeTree(cls(ctx).typeRef.appliedTo(tp))) + def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] = + if (ann.symbol == cls(ctx)) { val AppliedType(tycon, arg :: Nil) = ann.tree.tpe Some(arg) } else None } + /** Extractor for opaque alias annotations */ + object OpaqueAlias extends TypeHintExtractor(implicit ctx => defn.OpaqueAliasAnnot) + + /** Extractpr for linked type annotations */ + object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) + def makeSourceFile(path: String)(implicit ctx: Context) = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 802f8c9fedcc..9a582456f1c5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -685,6 +685,8 @@ class Definitions { def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass lazy val OpaqueAliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.OpaqueAlias") def OpaqueAliasAnnot(implicit ctx: Context) = OpaqueAliasAnnotType.symbol.asClass + lazy val LinkedTypeAnnotType = ctx.requiredClassRef("scala.annotation.internal.LinkedType") + def LinkedTypeAnnot(implicit ctx: Context) = LinkedTypeAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index d23c4c16098f..5abefbf00e95 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -143,9 +143,6 @@ object StdNames { val WHILE_PREFIX: N = "while$" val DEFAULT_EXCEPTION_NAME: N = "ex$" val INITIALIZER_PREFIX: N = "initial$" - val COMPANION_MODULE_METHOD: N = "companion$module" - val COMPANION_CLASS_METHOD: N = "companion$class" - val COMPANION_TYPE_METHOD: N = "companion$type" val BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f4f2b43a4ee4..6fdda2e07e6b 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -105,11 +105,14 @@ trait SymDenotations { this: Context => } /** Configurable: Accept stale symbol with warning if in IDE */ - def staleOK = Config.ignoreStaleInIDE && mode.is(Mode.Interactive) + def staleOK(denot: SingleDenotation) = + Config.ignoreStaleInIDE && mode.is(Mode.Interactive) || + denot.symbol.owner == defn.LinkedTypeAnnot + // LinkedType's type parameter might be stale if LinkedType itself is compiled since it is loaded early /** Possibly accept stale symbol with warning if in IDE */ def acceptStale(denot: SingleDenotation): Boolean = - staleOK && { + staleOK(denot) && { ctx.echo(denot.staleSymbolMsg) true } @@ -505,16 +508,6 @@ object SymDenotations { final def isAnonymousModuleVal(implicit ctx: Context) = this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) - /** Is this a companion class or type method or companion object method? - * These methods are generated by Symbols#synthesizeCompanionMethod - * and used in SymDenotations#companionClass and - * SymDenotations#companionModule . - */ - final def isCompanionMethod(implicit ctx: Context) = - name.toTermName == nme.COMPANION_CLASS_METHOD || - name.toTermName == nme.COMPANION_MODULE_METHOD || - name.toTermName == nme.COMPANION_TYPE_METHOD - /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods * and used in ElimErasedValueType. @@ -626,12 +619,9 @@ object SymDenotations { * - not an accessor * - not a label * - not an anonymous function - * - not a companion method */ final def isRealMethod(implicit ctx: Context) = - this.is(Method, butNot = AccessorOrLabel) && - !isAnonymousFunction && - !isCompanionMethod + this.is(Method, butNot = AccessorOrLabel) && !isAnonymousFunction /** Is this a getter? */ final def isGetter(implicit ctx: Context) = @@ -964,27 +954,29 @@ object SymDenotations { final def enclosingPackageClass(implicit ctx: Context): Symbol = if (this is PackageClass) symbol else owner.enclosingPackageClass + /** Register target as a companion */ + def registerCompanion(target: Symbol)(implicit ctx: Context) = + if (exists && target.exists && !unforcedIsAbsent && !target.unforcedIsAbsent && + !myAnnotations.exists(_.symbol == defn.LinkedTypeAnnot)) + addAnnotation(Annotation.LinkedType(target.typeRef)) + /** The module object with the same (term-) name as this class or module class, * and which is also defined in the same scope and compilation unit. * NoSymbol if this module does not exist. */ - final def companionModule(implicit ctx: Context): Symbol = { - if (this.flagsUNSAFE is Flags.Module) this.sourceModule - else { - val companionMethod = info.decls.denotsNamed(nme.COMPANION_MODULE_METHOD, selectPrivate).first - if (companionMethod.exists) - companionMethod.info.resultType.classSymbol.sourceModule - else - NoSymbol + final def companionModule(implicit ctx: Context): Symbol = + if (is(Module)) sourceModule + else getAnnotation(defn.LinkedTypeAnnot) match { + case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol.sourceModule + case _ => NoSymbol } - } - private def companionType(name: TermName)(implicit ctx: Context): Symbol = + private def companionType(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol - else { - val companionMethod = info.decls.denotsNamed(name, selectPrivate).first - if (companionMethod.exists) companionMethod.info.resultType.typeSymbol - else NoSymbol + else if (is(ModuleVal)) moduleClass.denot.companionType + else getAnnotation(defn.LinkedTypeAnnot) match { + case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol + case _ => NoSymbol } /** The class with the same (type-) name as this module or module class, @@ -992,14 +984,14 @@ object SymDenotations { * NoSymbol if this class does not exist. */ final def companionClass(implicit ctx: Context): Symbol = - companionType(nme.COMPANION_CLASS_METHOD).suchThat(_.isClass).symbol + companionType.suchThat(_.isClass).symbol /** The opaque type with the same (type-) name as this module or module class, * and which is also defined in the same scope and compilation unit. * NoSymbol if this type does not exist. */ final def companionOpaqueType(implicit ctx: Context): Symbol = - companionType(nme.COMPANION_TYPE_METHOD).suchThat(_.is(Opaque)).symbol + companionType.suchThat(_.is(Opaque)).symbol final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 5b7a6b311ab2..2bae0b511a72 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -187,15 +187,6 @@ trait Symbols { this: Context => val companionMethodFlags = Flags.Synthetic | Flags.Private | Flags.Method - def synthesizeCompanionMethod(name: Name, target: SymDenotation, owner: SymDenotation)(implicit ctx: Context) = - if (owner.exists && target.exists && !owner.unforcedIsAbsent && !target.unforcedIsAbsent) { - val existing = owner.unforcedDecls.lookup(name) - - existing.orElse{ - ctx.newSymbol(owner.symbol, name, companionMethodFlags , ExprType(target.typeRef)) - } - } else NoSymbol - /** Create a package symbol with associated package class * from its non-info fields and a lazy type for loading the package's members. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index a4566e729b72..2a668520f152 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -158,9 +158,6 @@ object TypeErasure { * - For $asInstanceOf : [T]T * - For $isInstanceOf : [T]Boolean * - For all abstract types : = ? - * - For companion methods : the erasure of their type with semiEraseVCs = false. - * The signature of these methods are used to keep a - * link between companions and should not be semi-erased. * - For Java-defined symbols: : the erasure of their type with isJava = true, * semiEraseVCs = false. Semi-erasure never happens in Java. * - For all other symbols : the semi-erasure of their types, with @@ -168,7 +165,7 @@ object TypeErasure { */ def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { val isJava = sym is JavaDefined - val semiEraseVCs = !isJava && !sym.isCompanionMethod + val semiEraseVCs = !isJava val erase = erasureFn(isJava, semiEraseVCs, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 2898db135a3b..efd48d0fa71c 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -164,10 +164,8 @@ class ClassfileParser( classInfo = parseAttributes(classRoot.symbol, classInfo) if (isAnnotation) addAnnotationConstructor(classInfo) - val companionClassMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, classRoot, moduleRoot) - if (companionClassMethod.exists) companionClassMethod.entered - val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, moduleRoot, classRoot) - if (companionModuleMethod.exists) companionModuleMethod.entered + classRoot.registerCompanion(moduleRoot.symbol) + moduleRoot.registerCompanion(classRoot.symbol) setClassInfo(classRoot, classInfo) setClassInfo(moduleRoot, staticInfo) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 88560b46b9ef..0feddd48eeef 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -619,8 +619,10 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot || ann.symbol == defn.OpaqueAliasAnnot - // inline bodies and opaque aliases are reconstituted automatically when unpickling + case _ => + val sym = ann.symbol + sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot || sym == defn.LinkedTypeAnnot + // these are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 5a31036991f5..7e6e7c8763e8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -752,14 +752,12 @@ class TreeUnpickler(reader: TastyReader, def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) if (companion.exists && isCodefined) { - if (sym is Module) { - if (companion.isClass) - sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) - else if (companion.is(Opaque)) - sym.registerCompanionMethod(nme.COMPANION_TYPE_METHOD, companion) + if (companion.isClass) + sym.registerCompanion(companion) + else if (sym.is(Module) && companion.is(Opaque)) { + sym.registerCompanion(companion) + companion.registerCompanion(sym) } - else - sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) } TypeDef(readTemplate(localCtx)) } else { diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index f7dbda218e0e..83c0baf4acec 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -120,11 +120,8 @@ object Scala2Unpickler { val scalacCompanion = denot.classSymbol.scalacLinkedClass def registerCompanionPair(module: Symbol, claz: Symbol) = { - import transform.SymUtils._ - module.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, claz) - if (claz.isClass) { - claz.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, module) - } + module.registerCompanion(claz) + if (claz.isClass) claz.registerCompanion(module) } if (denot.flagsUNSAFE is Module) { diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index 392d719cfefd..c590adf2206e 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -13,7 +13,7 @@ import scala.language.implicitConversions class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = - annots.filter(_.tpe != defn.SourceFileAnnotType) + super.filterModTextAnnots(annots).filter(_.tpe != defn.SourceFileAnnotType) override protected def blockText[T >: Untyped](trees: List[Trees.Tree[T]]): Text = { super.blockText(trees.filterNot(_.isInstanceOf[Closure[_]])) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48bc992f8a47..ce00faee2d16 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -694,7 +694,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } - protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = annots + protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = + annots filter { + case tpt: TypeTree[_] => tpt.typeOpt.typeSymbol != defn.LinkedTypeAnnot + case _ => true + } def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name)) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4be68b0879d4..cea2975da291 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -212,8 +212,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // Synthetic methods that are always present do not affect the API // and can therefore be ignored. - def alwaysPresent(s: Symbol) = - s.isCompanionMethod || (csym.is(ModuleClass) && s.isConstructor) + def alwaysPresent(s: Symbol) = csym.is(ModuleClass) && s.isConstructor val decls = cinfo.decls.filter(!alwaysPresent(_)) val apiDecls = apiDefinitions(decls) diff --git a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala index b88d46fdda6a..e21dbc783b0f 100644 --- a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala +++ b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala @@ -44,18 +44,6 @@ class RestoreScopes extends MiniPhase with IdentityDenotTransformer { thisPhase val cls = tree.symbol.asClass val pkg = cls.owner.asClass - // Bring back companion links - val companionClass = cls.info.decls.lookup(nme.COMPANION_CLASS_METHOD) - val companionModule = cls.info.decls.lookup(nme.COMPANION_MODULE_METHOD) - - if (companionClass.exists) { - restoredDecls.enter(companionClass) - } - - if (companionModule.exists) { - restoredDecls.enter(companionModule) - } - pkg.enter(cls) val cinfo = cls.classInfo tree.symbol.copySymDenotation( diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 880d7f686830..3873fbfc6fb4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -123,13 +123,6 @@ class SymUtils(val self: Symbol) extends AnyVal { self } - def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { - if (!self.unforcedDecls.lookup(name).exists) { - val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) - if (companionMethod.exists) companionMethod.entered - } - } - /** If this symbol is an enum value or a named class, register it as a child * in all direct parent classes which are sealed. * @param @late If true, register only inaccessible children (all others are already diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2fc388373f12..e8274d2861a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -370,10 +370,7 @@ class TreeChecker extends Phase with SymTransformer { checkOwner(impl.constr) def isNonMagicalMethod(x: Symbol) = - x.is(Method) && - !x.isCompanionMethod && - !x.isValueClassConvertMethod && - !(x.is(Macro) && ctx.phase.refChecked) + x.is(Method) && !x.isValueClassConvertMethod && !(x.is(Macro) && ctx.phase.refChecked) val symbolsNotDefined = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5a0ea210ba5d..2d7ba3347c4c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -621,13 +621,10 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (modl.isClass) - if (claz.isClass) { - ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered - ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered - } - else if (claz.is(Opaque)) - ctx.synthesizeCompanionMethod(nme.COMPANION_TYPE_METHOD, claz, modl).entered + if (modl.isClass && (claz.isClass || claz.is(Opaque))) { + modl.registerCompanion(claz) + claz.registerCompanion(modl) + } } def createCompanionLinks(implicit ctx: Context): Unit = { From c70bcb0b78f1184b33bc967c8ed06883cdce5084 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:02:04 +0100 Subject: [PATCH 09/54] Eliminate boxing for opaque types FirstTransform rewrites opaque type aliases to normal aliases, so no boxing is introduced in erasure. --- .../tools/dotc/transform/FirstTransform.scala | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 14bb0c00ceba..aaf3616cb596 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -15,6 +15,7 @@ import Contexts.Context import Symbols._ import SymDenotations._ import Decorators._ +import Annotations._ import dotty.tools.dotc.core.Annotations.ConcreteAnnotation import dotty.tools.dotc.core.Denotations.SingleDenotation import scala.collection.mutable @@ -34,13 +35,14 @@ object FirstTransform { * - eliminates some kinds of trees: Imports, NamedArgs * - stubs out native methods * - eliminates self tree in Template and self symbol in ClassInfo + * - rewrites opaque type aliases to normal alias types * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types * - drops branches of ifs using the rules * if (true) A else B ==> A * if (false) A else B ==> B */ -class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => +class FirstTransform extends MiniPhase with SymTransformer { thisPhase => import ast.tpd._ override def phaseName = FirstTransform.name @@ -57,12 +59,27 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => ctx } - /** eliminate self symbol in ClassInfo */ - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + /** Two transforms: + * 1. eliminate self symbol in ClassInfo + * 2. Rewrite opaque type aliases to normal alias types + */ + def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = sym.info match { case tp @ ClassInfo(_, _, _, _, self: Symbol) => - tp.derivedClassInfo(selfInfo = self.info) + sym.copySymDenotation(info = tp.derivedClassInfo(selfInfo = self.info)) + .copyCaches(sym, ctx.phase.next) case _ => - tp + if (sym.is(Opaque)) + sym.getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(Annotation.OpaqueAlias(rhs)) => + val result = sym.copySymDenotation(info = TypeAlias(rhs)) + result.removeAnnotation(defn.OpaqueAliasAnnot) + result.resetFlag(Opaque) + result.resetFlag(Deferred) + result + case _ => + sym + } + else sym } override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass From af5c199d574b68aa03e9a1fe05e499daca02444d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:43:24 +0100 Subject: [PATCH 10/54] Make OpaqueAlias and LinkedType special classes It's overall simpler to just define them internally in Definitions --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 8 ++++---- compiler/src/dotty/tools/dotc/core/StdNames.scala | 2 ++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 7 ++----- .../src/scala/annotation/internal/OpaqueAlias.scala | 11 ----------- 4 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 library/src/scala/annotation/internal/OpaqueAlias.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9a582456f1c5..22eebd20684a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -537,6 +537,8 @@ class Definitions { lazy val EqualsPatternClass = enterSpecialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType)) lazy val RepeatedParamClass = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) + lazy val LinkedTypeAnnot = enterSpecialPolyClass(tpnme.LINKED_TYPE, EmptyFlags, Seq(AnyType)) + lazy val OpaqueAliasAnnot = enterSpecialPolyClass(tpnme.OPAQUE_ALIAS, EmptyFlags, Seq(AnyType)) // fundamental classes lazy val StringClass = ctx.requiredClass("java.lang.String") @@ -683,10 +685,6 @@ class Definitions { def BodyAnnot(implicit ctx: Context) = BodyAnnotType.symbol.asClass lazy val ChildAnnotType = ctx.requiredClassRef("scala.annotation.internal.Child") def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass - lazy val OpaqueAliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.OpaqueAlias") - def OpaqueAliasAnnot(implicit ctx: Context) = OpaqueAliasAnnotType.symbol.asClass - lazy val LinkedTypeAnnotType = ctx.requiredClassRef("scala.annotation.internal.LinkedType") - def LinkedTypeAnnot(implicit ctx: Context) = LinkedTypeAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") @@ -1175,6 +1173,8 @@ class Definitions { AnyKindClass, RepeatedParamClass, ByNameParamClass2x, + LinkedTypeAnnot, + OpaqueAliasAnnot, AnyValClass, NullClass, NothingClass, diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 5abefbf00e95..f69c83c62235 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -189,6 +189,8 @@ object StdNames { final val EQUALS_PATTERN: N = "" final val LOCAL_CHILD: N = "" final val REPEATED_PARAM_CLASS: N = "" + final val OPAQUE_ALIAS: N = "" + final val LINKED_TYPE: N = "" final val WILDCARD_STAR: N = "_*" final val REIFY_TREECREATOR_PREFIX: N = "$treecreator" final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 6fdda2e07e6b..da6eba795639 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -105,14 +105,11 @@ trait SymDenotations { this: Context => } /** Configurable: Accept stale symbol with warning if in IDE */ - def staleOK(denot: SingleDenotation) = - Config.ignoreStaleInIDE && mode.is(Mode.Interactive) || - denot.symbol.owner == defn.LinkedTypeAnnot - // LinkedType's type parameter might be stale if LinkedType itself is compiled since it is loaded early + def staleOK = Config.ignoreStaleInIDE && mode.is(Mode.Interactive) /** Possibly accept stale symbol with warning if in IDE */ def acceptStale(denot: SingleDenotation): Boolean = - staleOK(denot) && { + staleOK && { ctx.echo(denot.staleSymbolMsg) true } diff --git a/library/src/scala/annotation/internal/OpaqueAlias.scala b/library/src/scala/annotation/internal/OpaqueAlias.scala deleted file mode 100644 index a6aaa54c6b66..000000000000 --- a/library/src/scala/annotation/internal/OpaqueAlias.scala +++ /dev/null @@ -1,11 +0,0 @@ -package scala.annotation.internal - -import scala.annotation.Annotation - -/** An annotation to record the right-hand side of an opaque type. Given - * - * opaque type T = U - * - * the info of `T` is `Nothing..Any`, but `T` carries the annotation `OpaqueAlias[U]` - */ -class OpaqueAlias[T] extends Annotation From f2cf353e91d2ee83beaa1af827fee12895838908 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:32:27 +0100 Subject: [PATCH 11/54] Make implicit scope include companion objects of opaque types --- .../tools/dotc/core/SymDenotations.scala | 5 +++ .../dotty/tools/dotc/typer/Implicits.scala | 33 ++++++++++--------- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- tests/neg/tagging.scala | 2 -- tests/pos/opaque.scala | 4 +-- tests/pos/tagging.scala | 2 -- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index da6eba795639..2082c5ad600f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -551,6 +551,11 @@ object SymDenotations { /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(implicit ctx: Context) = this is DeferredOrTypeParam + /** Can this symbol have a companion module? + * This is the case if it is a class or an opaque type alias. + */ + final def canHaveCompanion(implicit ctx: Context) = isClass || is(Opaque) + /** Is this the denotation of a self symbol of some class? * This is the case if one of two conditions holds: * 1. It is the symbol referred to in the selfInfo part of the ClassInfo diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f11f75b704c4..f50360bdd212 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -394,7 +394,7 @@ trait ImplicitRunInfo { self: Run => override implicit protected val ctx: Context = liftingCtx override def stopAtStatic = true def apply(tp: Type) = tp match { - case tp: TypeRef if tp.symbol.isAbstractOrAliasType => + case tp: TypeRef if !tp.symbol.canHaveCompanion => val pre = tp.prefix def joinClass(tp: Type, cls: ClassSymbol) = AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner)) @@ -402,7 +402,7 @@ trait ImplicitRunInfo { self: Run => (lead /: tp.classSymbols)(joinClass) case tp: TypeVar => apply(tp.underlying) - case tp: AppliedType if !tp.tycon.typeSymbol.isClass => + case tp: AppliedType if !tp.tycon.typeSymbol.canHaveCompanion => def applyArg(arg: Type) = arg match { case TypeBounds(lo, hi) => AndType.make(lo, hi) case WildcardType(TypeBounds(lo, hi)) => AndType.make(lo, hi) @@ -440,21 +440,24 @@ trait ImplicitRunInfo { self: Run => case tp: NamedType => val pre = tp.prefix comps ++= iscopeRefs(pre) - def addClassScope(cls: ClassSymbol): Unit = { - def addRef(companion: TermRef): Unit = { - val compSym = companion.symbol - if (compSym is Package) - addRef(companion.select(nme.PACKAGE)) - else if (compSym.exists) - comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] - } - def addParentScope(parent: Type): Unit = - iscopeRefs(tp.baseType(parent.typeSymbol)) foreach addRef - val companion = cls.companionModule + def addRef(companion: TermRef): Unit = { + val compSym = companion.symbol + if (compSym is Package) + addRef(companion.select(nme.PACKAGE)) + else if (compSym.exists) + comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] + } + def addCompanionOf(sym: Symbol) = { + val companion = sym.companionModule if (companion.exists) addRef(companion.termRef) - cls.classParents foreach addParentScope } - tp.classSymbols(liftingCtx) foreach addClassScope + def addClassScope(cls: ClassSymbol): Unit = { + addCompanionOf(cls) + for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.typeSymbol))) + addRef(ref) + } + if (tp.widen.typeSymbol.is(Opaque)) addCompanionOf(tp.widen.typeSymbol) + else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2d7ba3347c4c..5cd627e972ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -621,7 +621,7 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (modl.isClass && (claz.isClass || claz.is(Opaque))) { + if (modl.isClass && claz.canHaveCompanion) { modl.registerCompanion(claz) claz.registerCompanion(modl) } diff --git a/tests/neg/tagging.scala b/tests/neg/tagging.scala index 72913872c36f..6ad97fde3632 100644 --- a/tests/neg/tagging.scala +++ b/tests/neg/tagging.scala @@ -15,8 +15,6 @@ object tagging { ct } - import Tagged._ - type @@[S, T] = Tagged[S, T] implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 15d0d38d0ae4..36c7ba6d8746 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -23,10 +23,10 @@ object opaquetypes { } object usesites { import opaquetypes._ - import Logarithm.LogarithmOps // todo: drop val l = Logarithm(1.0) val l2 = Logarithm(2.0) - val l3 = l + l2 + val l3 = l * l2 + val l4 = l * l2 val d = l3.toDouble val l4: Logarithm = (1.0).asInstanceOf[Logarithm] } diff --git a/tests/pos/tagging.scala b/tests/pos/tagging.scala index da2ba65f3ede..3a9caa2129f9 100644 --- a/tests/pos/tagging.scala +++ b/tests/pos/tagging.scala @@ -15,8 +15,6 @@ object tagging { ct } - import Tagged._ - type @@[S, T] = Tagged[S, T] implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { From 71ae9433a652fb568fae8b70f69c7fd80397481f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:57:18 +0100 Subject: [PATCH 12/54] Add reference documentation --- docs/docs/reference/opaques.md | 56 ++++++++++++++++++++++++++++++++++ docs/sidebar.yml | 2 ++ tests/neg/opaque.scala | 29 ++++++++++++++++++ tests/pos/opaque.scala | 2 +- 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 docs/docs/reference/opaques.md diff --git a/docs/docs/reference/opaques.md b/docs/docs/reference/opaques.md new file mode 100644 index 000000000000..0acaca050f3d --- /dev/null +++ b/docs/docs/reference/opaques.md @@ -0,0 +1,56 @@ +--- +layout: doc-page +title: "Opaque Type Aliases" +--- + +Opaque types aliases provide type abstraction without any overhead. Example: + +```scala +opaque type Logarithm = Double +``` + +This introduces `Logarithm` as a new type, which is implemented as `Double` but is different from it. The fact that `Logarithm` is the same as `Double` is only known in the companion object of `Logarithm`. Here is a possible companion object: + +```scala +object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(`this`) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(`this` + that) + } +} +``` + +The companion object contains with the `apply` and `safe` methods ways to convert from doubles to `Logarithm` values. It also adds an `exponent` function and a decorator that implements `+` and `*` on logarithm values, as well as a conversion `toDouble`. All this is possible because within object `Logarithm`, the type `Logarithm` is just an alias of `Double`. + +Outside the companion object, `Logarithm` is treated as a new abstract type. So the +following operations would be valid because they use functionality implemented in the `Logarithm` object. + +```scala + val l = Logarithm(1.0) + val l3 = l * l2 + val l4 = l + l2 +``` + +But the following operations would lead to type errors: + +```scala +val d: Double = l // error: found: Logarithm, required: Double +val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm +l * 2 // error: found: Int(2), required: Logarithm +l / l2 // error: `/` is not a member fo Logarithm +``` + +For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html). \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index ca456b11af65..e247849662e0 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -47,6 +47,8 @@ sidebar: url: docs/reference/inline.html - title: Meta Programming url: docs/reference/principled-meta-programming.html + - title: Opaque Type Aliases + url: docs/reference/opaques.html - title: By-Name Implicits url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala index f1117deffef2..efb06e8582c9 100644 --- a/tests/neg/opaque.scala +++ b/tests/neg/opaque.scala @@ -17,3 +17,32 @@ object opaquetypes { } +object logs { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(`this`) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(`this` + that) + } + } + + val l = Logarithm(2.0) + val d: Double = l // error: found: Logarithm, required: Double + val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm + l * 2 // error: found: Int(2), required: Logarithm + l / l2 // error: `/` is not a member fo Logarithm +} diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 36c7ba6d8746..403053bff643 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -26,7 +26,7 @@ object usesites { val l = Logarithm(1.0) val l2 = Logarithm(2.0) val l3 = l * l2 - val l4 = l * l2 + val l4 = l + l2 val d = l3.toDouble val l4: Logarithm = (1.0).asInstanceOf[Logarithm] } From 89dd5375878f5650db34dff18da65d5fb6fd01d7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:08:42 +0100 Subject: [PATCH 13/54] Fix test --- tests/pos/opaque.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 403053bff643..036f75b1cc05 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -1,3 +1,4 @@ +import Predef.{any2stringadd => _, _} object opaquetypes { opaque type Logarithm = Double @@ -26,7 +27,10 @@ object usesites { val l = Logarithm(1.0) val l2 = Logarithm(2.0) val l3 = l * l2 - val l4 = l + l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd val d = l3.toDouble - val l4: Logarithm = (1.0).asInstanceOf[Logarithm] + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] } From 893eebb07eb49584301d6defdcc028054e3e4768 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:10:15 +0100 Subject: [PATCH 14/54] Fix indentation --- docs/docs/reference/opaques.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/reference/opaques.md b/docs/docs/reference/opaques.md index 0acaca050f3d..e4a2867049ef 100644 --- a/docs/docs/reference/opaques.md +++ b/docs/docs/reference/opaques.md @@ -47,10 +47,10 @@ following operations would be valid because they use functionality implemented i But the following operations would lead to type errors: ```scala -val d: Double = l // error: found: Logarithm, required: Double -val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm -l * 2 // error: found: Int(2), required: Logarithm -l / l2 // error: `/` is not a member fo Logarithm + val d: Double = l // error: found: Logarithm, required: Double + val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm + l * 2 // error: found: Int(2), required: Logarithm + l / l2 // error: `/` is not a member fo Logarithm ``` For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html). \ No newline at end of file From 77317b068b96b1f1067c6009b6e53906073325b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 08:51:34 +0100 Subject: [PATCH 15/54] Treat companions of opaque types specially Make them part of the OpaqueAlias annotation. This is has two benefits - opaque types stop being companions after FirstTransform. Previously, the LinkedType annotations would persist even though the Opaque flag was reset. - we gain the freedom to implement companions between classes differently. --- .../dotty/tools/dotc/core/Annotations.scala | 18 ++++++++++++++++-- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotty/tools/dotc/core/SymDenotations.scala | 18 +++++++++++++----- .../tools/dotc/core/tasty/TreeUnpickler.scala | 13 +++---------- .../tools/dotc/transform/FirstTransform.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index a8f46a3ba52f..fec280eef80b 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -186,9 +186,23 @@ object Annotations { } /** Extractor for opaque alias annotations */ - object OpaqueAlias extends TypeHintExtractor(implicit ctx => defn.OpaqueAliasAnnot) + object OpaqueAlias { + def apply(tp: Type, companion: Symbol)(implicit ctx: Context): Annotation = { + val arg = if (companion.exists) RefinedType(tp, nme.companion, companion.termRef) else tp + Annotation(TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg))) + } + def unapply(ann: Annotation)(implicit ctx: Context): Option[(Type, Symbol)] = + if (ann.symbol == defn.OpaqueAliasAnnot) { + ann.tree.tpe match { + case AppliedType(_, RefinedType(tp, nme.companion, ref) :: Nil) => Some((tp, ref.termSymbol)) + case AppliedType(_, tp :: Nil) => Some((tp, NoSymbol)) + case _ => None + } + } + else None + } - /** Extractpr for linked type annotations */ + /** Extractor for linked type annotations */ object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) def makeSourceFile(path: String)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index f69c83c62235..54bbc139701a 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -395,6 +395,7 @@ object StdNames { val classOf: N = "classOf" val clone_ : N = "clone" // val conforms : N = "conforms" // Dotty deviation: no special treatment of conforms, so the occurrence of the name here would cause to unintended implicit shadowing. Should find a less common name for it in Predef. + val companion: N = "companion" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2082c5ad600f..8855485ee5c8 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -391,7 +391,8 @@ object SymDenotations { if (is(Opaque)) { info match { case tp @ TypeAlias(alias) => - addAnnotation(Annotation.OpaqueAlias(alias)) + val companion = companionNamed(name.moduleClassName).sourceModule + addAnnotation(Annotation.OpaqueAlias(alias, companion)) info = TypeBounds(defn.NothingType, abstractRHS(alias)) setFlag(Deferred) case _ => @@ -968,10 +969,17 @@ object SymDenotations { */ final def companionModule(implicit ctx: Context): Symbol = if (is(Module)) sourceModule - else getAnnotation(defn.LinkedTypeAnnot) match { - case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol.sourceModule - case _ => NoSymbol - } + else if (is(Opaque)) + getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(Annotation.OpaqueAlias(_, ref)) => ref + case _ => NoSymbol + } + else + getAnnotation(defn.LinkedTypeAnnot) match { + case Some(Annotation.LinkedType(linked: TypeRef)) => + linked.symbol.sourceModule + case _ => NoSymbol + } private def companionType(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7e6e7c8763e8..9051c4bb91fd 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -749,16 +749,9 @@ class TreeUnpickler(reader: TastyReader, // The only case to check here is if `sym` is a root. In this case // `companion` might have been entered by the environment but it might // be missing from the Tasty file. So we check explicitly for that. - def isCodefined = - roots.contains(companion.denot) == seenRoots.contains(companion) - if (companion.exists && isCodefined) { - if (companion.isClass) - sym.registerCompanion(companion) - else if (sym.is(Module) && companion.is(Opaque)) { - sym.registerCompanion(companion) - companion.registerCompanion(sym) - } - } + def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) + + if (companion.canHaveCompanion && isCodefined) sym.registerCompanion(companion) TypeDef(readTemplate(localCtx)) } else { sym.info = TypeBounds.empty // needed to avoid cyclic references when unpicklin rhs, see i3816.scala diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index aaf3616cb596..1f63a093aa79 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -70,7 +70,7 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => case _ => if (sym.is(Opaque)) sym.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs)) => + case Some(Annotation.OpaqueAlias(rhs, _)) => val result = sym.copySymDenotation(info = TypeAlias(rhs)) result.removeAnnotation(defn.OpaqueAliasAnnot) result.resetFlag(Opaque) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5cd627e972ba..fbbc2c3242be 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -111,7 +111,7 @@ trait NamerContextOps { this: Context => if (ctx.owner.is(Module)) { val opaq = ctx.owner.companionOpaqueType opaq.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs)) => + case Some(Annotation.OpaqueAlias(rhs, _)) => localCtx = localCtx.setFreshGADTBounds localCtx.gadt.setBounds(opaq, TypeAlias(rhs)) case _ => From 4006366206401d41849cb6fbefa186d3e8c36cdf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 09:29:57 +0100 Subject: [PATCH 16/54] Replace annotation extractor with access methods --- .../dotty/tools/dotc/core/Annotations.scala | 17 ---------- .../src/dotty/tools/dotc/core/StdNames.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 31 ++++++++++++++++--- .../tools/dotc/transform/FirstTransform.scala | 17 ++++------ .../src/dotty/tools/dotc/typer/Namer.scala | 9 +++--- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index fec280eef80b..18b47355470c 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -185,23 +185,6 @@ object Annotations { else None } - /** Extractor for opaque alias annotations */ - object OpaqueAlias { - def apply(tp: Type, companion: Symbol)(implicit ctx: Context): Annotation = { - val arg = if (companion.exists) RefinedType(tp, nme.companion, companion.termRef) else tp - Annotation(TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg))) - } - def unapply(ann: Annotation)(implicit ctx: Context): Option[(Type, Symbol)] = - if (ann.symbol == defn.OpaqueAliasAnnot) { - ann.tree.tpe match { - case AppliedType(_, RefinedType(tp, nme.companion, ref) :: Nil) => Some((tp, ref.termSymbol)) - case AppliedType(_, tp :: Nil) => Some((tp, NoSymbol)) - case _ => None - } - } - else None - } - /** Extractor for linked type annotations */ object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 54bbc139701a..6458a0676a9b 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -247,6 +247,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val DEFAULT_CASE: N = "defaultCase$" @@ -395,7 +396,6 @@ object StdNames { val classOf: N = "classOf" val clone_ : N = "clone" // val conforms : N = "conforms" // Dotty deviation: no special treatment of conforms, so the occurrence of the name here would cause to unintended implicit shadowing. Should find a less common name for it in Predef. - val companion: N = "companion" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 8855485ee5c8..16a083661762 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -390,9 +390,12 @@ object SymDenotations { } if (is(Opaque)) { info match { - case tp @ TypeAlias(alias) => + case TypeAlias(alias) => val companion = companionNamed(name.moduleClassName).sourceModule - addAnnotation(Annotation.OpaqueAlias(alias, companion)) + val arg = + if (companion.exists) RefinedType(alias, nme.COMPANION, companion.termRef) + else alias + addAnnotation(Annotation(tpd.TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg)))) info = TypeBounds(defn.NothingType, abstractRHS(alias)) setFlag(Deferred) case _ => @@ -971,8 +974,13 @@ object SymDenotations { if (is(Module)) sourceModule else if (is(Opaque)) getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(_, ref)) => ref - case _ => NoSymbol + case Some(ann) => + val AppliedType(_, arg :: Nil) = ann.tree.tpe + arg match { + case RefinedType(tp, _, ref) => ref.termSymbol + case _ => NoSymbol + } + case None => NoSymbol } else getAnnotation(defn.LinkedTypeAnnot) match { @@ -1052,6 +1060,21 @@ object SymDenotations { final def enclosingSubClass(implicit ctx: Context) = ctx.owner.ownersIterator.findSymbol(_.isSubClass(symbol)) + /** The alias of an opaque type */ + def opaqueAlias(implicit ctx: Context): Type = { + if (is(Opaque)) + getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(ann) => + val AppliedType(_, arg :: Nil) = ann.tree.tpe + arg match { + case RefinedType(tp, nme.COMPANION, _) => tp + case tp => tp + } + case None => NoType + } + else NoType + } + /** The non-private symbol whose name and type matches the type of this symbol * in the given class. * @param inClass The class containing the result symbol's definition diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 1f63a093aa79..b8e0819471fb 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -68,17 +68,12 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => sym.copySymDenotation(info = tp.derivedClassInfo(selfInfo = self.info)) .copyCaches(sym, ctx.phase.next) case _ => - if (sym.is(Opaque)) - sym.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs, _)) => - val result = sym.copySymDenotation(info = TypeAlias(rhs)) - result.removeAnnotation(defn.OpaqueAliasAnnot) - result.resetFlag(Opaque) - result.resetFlag(Deferred) - result - case _ => - sym - } + if (sym.is(Opaque)) { + val result = sym.copySymDenotation(info = TypeAlias(sym.opaqueAlias)) + result.removeAnnotation(defn.OpaqueAliasAnnot) + result.resetFlag(Opaque | Deferred) + result + } else sym } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fbbc2c3242be..b573d4087394 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -110,11 +110,10 @@ trait NamerContextOps { this: Context => } if (ctx.owner.is(Module)) { val opaq = ctx.owner.companionOpaqueType - opaq.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs, _)) => - localCtx = localCtx.setFreshGADTBounds - localCtx.gadt.setBounds(opaq, TypeAlias(rhs)) - case _ => + val alias = opaq.opaqueAlias + if (alias.exists) { + localCtx = localCtx.setFreshGADTBounds + localCtx.gadt.setBounds(opaq, TypeAlias(alias)) } } localCtx From fa099ed58a4771867957e9517c03002ba4e9deb4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 10:34:28 +0100 Subject: [PATCH 17/54] Simplify companion scheme Use a field in ClassDenotation instead of an annotation --- .../dotty/tools/dotc/core/Annotations.scala | 15 -------- .../dotty/tools/dotc/core/Definitions.scala | 2 -- .../src/dotty/tools/dotc/core/Flags.scala | 4 ++- .../tools/dotc/core/SymDenotations.scala | 34 +++++++++++-------- .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../core/unpickleScala2/Scala2Unpickler.scala | 7 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 6 +--- .../src/dotty/tools/dotc/typer/Namer.scala | 8 ++--- 9 files changed, 31 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 18b47355470c..2da8c95f0b71 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -173,21 +173,6 @@ object Annotations { else None } - /** A generic extractor for annotations carrying types */ - class TypeHintExtractor(cls: Context => ClassSymbol) { - def apply(tp: Type)(implicit ctx: Context): Annotation = - Annotation(TypeTree(cls(ctx).typeRef.appliedTo(tp))) - def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] = - if (ann.symbol == cls(ctx)) { - val AppliedType(tycon, arg :: Nil) = ann.tree.tpe - Some(arg) - } - else None - } - - /** Extractor for linked type annotations */ - object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) - def makeSourceFile(path: String)(implicit ctx: Context) = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 22eebd20684a..e2a4968226bc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -537,7 +537,6 @@ class Definitions { lazy val EqualsPatternClass = enterSpecialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType)) lazy val RepeatedParamClass = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) - lazy val LinkedTypeAnnot = enterSpecialPolyClass(tpnme.LINKED_TYPE, EmptyFlags, Seq(AnyType)) lazy val OpaqueAliasAnnot = enterSpecialPolyClass(tpnme.OPAQUE_ALIAS, EmptyFlags, Seq(AnyType)) // fundamental classes @@ -1173,7 +1172,6 @@ class Definitions { AnyKindClass, RepeatedParamClass, ByNameParamClass2x, - LinkedTypeAnnot, OpaqueAliasAnnot, AnyValClass, NullClass, diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index ed702f328558..c9e2f1926fb6 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -257,6 +257,8 @@ object Flags { /** An opqaue type */ final val Opaque = typeFlag(12, "opaque") + final val MutableOrOpaque = Mutable.toCommonFlags + /** Symbol is local to current class (i.e. private[this] or protected[this] * pre: Private or Protected are also set */ @@ -460,7 +462,7 @@ object Flags { /** Flags guaranteed to be set upon symbol creation */ final val FromStartFlags = Module | Package | Deferred | MethodOrHKCommon | Param | ParamAccessor.toCommonFlags | - Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | + Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | Erroneous | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 16a083661762..80c6873b5dca 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -960,11 +960,12 @@ object SymDenotations { final def enclosingPackageClass(implicit ctx: Context): Symbol = if (this is PackageClass) symbol else owner.enclosingPackageClass - /** Register target as a companion */ - def registerCompanion(target: Symbol)(implicit ctx: Context) = - if (exists && target.exists && !unforcedIsAbsent && !target.unforcedIsAbsent && - !myAnnotations.exists(_.symbol == defn.LinkedTypeAnnot)) - addAnnotation(Annotation.LinkedType(target.typeRef)) + /** Register target as a companion; overridden in ClassDenotation */ + def registerCompanion(target: Symbol)(implicit ctx: Context) = () + + /** The registered companion; overridden in ClassDenotation */ + def registeredCompanion(implicit ctx: Context): Symbol = NoSymbol + def registeredCompanion_=(c: Symbol): Unit = () /** The module object with the same (term-) name as this class or module class, * and which is also defined in the same scope and compilation unit. @@ -982,20 +983,12 @@ object SymDenotations { } case None => NoSymbol } - else - getAnnotation(defn.LinkedTypeAnnot) match { - case Some(Annotation.LinkedType(linked: TypeRef)) => - linked.symbol.sourceModule - case _ => NoSymbol - } + else registeredCompanion.sourceModule private def companionType(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol else if (is(ModuleVal)) moduleClass.denot.companionType - else getAnnotation(defn.LinkedTypeAnnot) match { - case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol - case _ => NoSymbol - } + else registeredCompanion /** The class with the same (type-) name as this module or module class, * and which is also defined in the same scope and compilation unit. @@ -1287,6 +1280,7 @@ object SymDenotations { val annotations1 = if (annotations != null) annotations else this.annotations val d = ctx.SymDenotation(symbol, owner, name, initFlags1, info1, privateWithin1) d.annotations = annotations1 + d.registeredCompanion = registeredCompanion d } @@ -1852,6 +1846,16 @@ object SymDenotations { .copyCaches(this, phase.next) .installAfter(phase) } + + private[this] var myCompanion: Symbol = NoSymbol + + /** Register companion class */ + override def registerCompanion(companion: Symbol)(implicit ctx: Context) = + if (companion.canHaveCompanion && !unforcedIsAbsent && !companion.unforcedIsAbsent) + myCompanion = companion + + override def registeredCompanion(implicit ctx: Context) = { ensureCompleted(); myCompanion } + override def registeredCompanion_=(c: Symbol) = { myCompanion = c } } /** The denotation of a package class. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0feddd48eeef..d3714e210395 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -621,7 +621,7 @@ class TreePickler(pickler: TastyPickler) { // See tests/pickling/i3149.scala case _ => val sym = ann.symbol - sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot || sym == defn.LinkedTypeAnnot + sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot // these are reconstituted automatically when unpickling } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9051c4bb91fd..ef092898efa3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -751,7 +751,7 @@ class TreeUnpickler(reader: TastyReader, // be missing from the Tasty file. So we check explicitly for that. def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) - if (companion.canHaveCompanion && isCodefined) sym.registerCompanion(companion) + if (companion.exists && isCodefined) sym.registerCompanion(companion) TypeDef(readTemplate(localCtx)) } else { sym.info = TypeBounds.empty // needed to avoid cyclic references when unpicklin rhs, see i3816.scala diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 83c0baf4acec..e1361d513bb1 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -121,14 +121,13 @@ object Scala2Unpickler { def registerCompanionPair(module: Symbol, claz: Symbol) = { module.registerCompanion(claz) - if (claz.isClass) claz.registerCompanion(module) + claz.registerCompanion(module) } - if (denot.flagsUNSAFE is Module) { + if (denot.flagsUNSAFE is Module) registerCompanionPair(denot.classSymbol, scalacCompanion) - } else { + else registerCompanionPair(scalacCompanion, denot.classSymbol) - } tempInfo.finalize(denot, normalizedParents) // install final info, except possibly for typeparams ordering denot.ensureTypeParamsInCorrectOrder() diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ce00faee2d16..48bc992f8a47 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -694,11 +694,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } - protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = - annots filter { - case tpt: TypeTree[_] => tpt.typeOpt.typeSymbol != defn.LinkedTypeAnnot - case _ => true - } + protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = annots def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index b573d4087394..fe605981317f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -620,11 +620,9 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (modl.isClass && claz.canHaveCompanion) { - modl.registerCompanion(claz) - claz.registerCompanion(modl) - } - } + modl.registerCompanion(claz) + claz.registerCompanion(modl) + } def createCompanionLinks(implicit ctx: Context): Unit = { val classDef = mutable.Map[TypeName, TypeDef]() From 55cd70f7ba9f7b1df078971868349011d4132a62 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 17:58:01 +0100 Subject: [PATCH 18/54] add test --- tests/neg/opaque.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala index efb06e8582c9..3b675f563813 100644 --- a/tests/neg/opaque.scala +++ b/tests/neg/opaque.scala @@ -1,4 +1,5 @@ object opaquetypes { + opaque val x: Int = 1 // error opaque class Foo // error @@ -7,6 +8,8 @@ object opaquetypes { opaque type U <: String // error + opaque type Fix[F[_]] = F[Fix[F]] // error: cyclic + opaque type O = String val s: O = "" // error From 6161baf1fb67d84f57ee86d9a9398fcbc5a16b43 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 17:24:21 +0100 Subject: [PATCH 19/54] Follow GADT bounds when computing members When computing the member denotation of a selection of a TypeRef `T`, if the normal scheme fails and `T` has GADT bounds, compute the member in the upper bound instead. This is needed to make the opaque-probability test work. Add this test, as well as some others coming from SIP 35. --- .../src/dotty/tools/dotc/core/Types.scala | 19 +++- .../dotty/tools/dotc/typer/ProtoTypes.scala | 7 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 8 +- .../src/dotty/tools/dotc/typer/Typer.scala | 7 +- tests/pending/pos/opaque-recursive.scala | 16 ++++ tests/pos/opaque-digits.scala | 22 +++++ tests/pos/opaque-goups.scala | 95 +++++++++++++++++++ tests/pos/opaque-nullable.scala | 26 +++++ tests/pos/opaque-propability.scala | 43 +++++++++ 9 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 tests/pending/pos/opaque-recursive.scala create mode 100644 tests/pos/opaque-digits.scala create mode 100644 tests/pos/opaque-goups.scala create mode 100644 tests/pos/opaque-nullable.scala create mode 100644 tests/pos/opaque-propability.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2f3e13c89847..c3e50d8ff0ba 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -263,7 +263,7 @@ object Types { } /** Is some part of this type produced as a repair for an error? */ - final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) + def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) /** Does the type carry an annotation that is an instance of `cls`? */ @tailrec final def hasAnnotation(cls: ClassSymbol)(implicit ctx: Context): Boolean = stripTypeVar match { @@ -1290,6 +1290,23 @@ object Types { */ def deepenProto(implicit ctx: Context): Type = this + /** If this is a TypeRef or an Application of a GADT-bound type, replace the + * GADT reference by its upper GADT bound. Otherwise NoType. + */ + def followGADT(implicit ctx: Context): Type = widenDealias match { + case site: TypeRef if site.symbol.is(Opaque) => + ctx.gadt.bounds(site.symbol) match { + case TypeBounds(_, hi) => hi + case _ => NoType + } + case AppliedType(tycon, args) => + val tycon1 = tycon.followGADT + if (tycon1.exists) tycon1.appliedTo(args) + else NoType + case _ => + NoType + } + // ----- Substitutions ----------------------------------------------------- /** Substitute all types that refer in their symbol attribute to diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5aa4a2fbfd31..26d6176d4551 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -6,6 +6,7 @@ import core._ import ast._ import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps._, Symbols._ import NameKinds.DepParamName +import SymDenotations.NoDenotation import Trees._ import Constants._ import Scopes._ @@ -128,8 +129,9 @@ object ProtoTypes { memberProto.isRef(defn.UnitClass) || compat.normalizedCompatible(NamedType(tp1, name, m), memberProto) // Note: can't use `m.info` here because if `m` is a method, `m.info` - // loses knowledge about `m`'s default arguments. + // loses knowledge about `m`'s default arguments. || mbr match { // hasAltWith inlined for performance + case NoDenotation => tp1.exists && isMatchedBy(tp1.followGADT) case mbr: SingleDenotation => mbr.exists && qualifies(mbr) case _ => mbr hasAltWith qualifies } @@ -318,6 +320,9 @@ object ProtoTypes { def isDropped: Boolean = toDrop + override def isErroneous(implicit ctx: Context): Boolean = + myTypedArgs.tpes.exists(_.widen.isErroneous) + override def toString = s"FunProto(${args mkString ","} => $resultType)" def map(tm: TypeMap)(implicit ctx: Context): FunProto = diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 61502cddf482..b3dd9cfbd9e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -236,8 +236,12 @@ trait TypeAssigner { if (reallyExists(mbr)) site.select(name, mbr) else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { TryDynamicCallType - } else { - if (site.isErroneous || name.toTermName == nme.ERROR) UnspecifiedErrorType + } + else { + val site1 = site.followGADT + if (site1.exists) selectionType(site1, name, pos) + else if (site.isErroneous || name.toTermName == nme.ERROR) + UnspecifiedErrorType else { def kind = if (name.isTypeName) "type" else "value" def addendum = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 431721dc44e6..a8c627241595 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2114,8 +2114,11 @@ class Typer extends Namer noMatches } case alts => - val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) - errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType) + else { + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + } } } diff --git a/tests/pending/pos/opaque-recursive.scala b/tests/pending/pos/opaque-recursive.scala new file mode 100644 index 000000000000..0111fce09bef --- /dev/null +++ b/tests/pending/pos/opaque-recursive.scala @@ -0,0 +1,16 @@ +object opaquetypes { + + opaque type Fix[F[_]] = F[Fix2[F]] + + opaque type Fix2[F[_]] = Fix[F] + + object Fix { + def unfold[F[_]](x: Fix[F]): F[Fix] + } + + object Fix2 { + def unfold[F[_]](x: Fix2[F]: Fix[F] = x + def fold[F[_]](x: Fix[F]: Fix2[F] = x + } + +} diff --git a/tests/pos/opaque-digits.scala b/tests/pos/opaque-digits.scala new file mode 100644 index 000000000000..29eed387dc2e --- /dev/null +++ b/tests/pos/opaque-digits.scala @@ -0,0 +1,22 @@ +object pkg { + + import Character.{isAlphabetic, isDigit} + + class Alphabetic private[pkg] (val value: String) extends AnyVal + + object Alphabetic { + def fromString(s: String): Option[Alphabetic] = + if (s.forall(isAlphabetic(_))) Some(new Alphabetic(s)) + else None + } + + opaque type Digits = String + + object Digits { + def fromString(s: String): Option[Digits] = + if (s.forall(isDigit(_))) Some(s) + else None + + def asString(d: Digits): String = d + } +} \ No newline at end of file diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-goups.scala new file mode 100644 index 000000000000..4391d756494b --- /dev/null +++ b/tests/pos/opaque-goups.scala @@ -0,0 +1,95 @@ +package object groups { + trait Semigroup[A] { + def combine(x: A, y: A): A + } + + object Semigroup { + def instance[A](f: (A, A) => A): Semigroup[A] = + new Semigroup[A] { + def combine(x: A, y: A): A = f(x, y) + } + } + + type Id[A] = A + + trait Wrapping[F[_]] { + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](ga: G[F[A]]): G[A] + } + + abstract class Wrapper[F[_]] { self => + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](gfa: G[F[A]]): G[A] + + final def apply[A](a: A): F[A] = wraps[Id, A](a) + + implicit object WrapperWrapping extends Wrapping[F] { + def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga) + def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga) + } + } + + opaque type First[A] = A + object First extends Wrapper[First] { + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga + def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa + implicit def firstSemigroup[A]: Semigroup[First[A]] = + Semigroup.instance((x, y) => x) + } + + opaque type Last[A] = A + object Last extends Wrapper[Last] { + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } + + opaque type Min[A] = A + object Min extends Wrapper[Min] { + def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga + def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa + implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] = + Semigroup.instance(o.min) + } + + opaque type Max[A] = A + object Max extends Wrapper[Max] { + def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga + def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa + implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] = + Semigroup.instance(o.max) + } + + opaque type Plus[A] = A + object Plus extends Wrapper[Plus] { + def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga + def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa + implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] = + Semigroup.instance(n.plus) + } + + opaque type Times[A] = A + object Times extends Wrapper[Times] { + def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga + def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa + implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] = + Semigroup.instance(n.times) + } + + opaque type Reversed[A] = A + object Reversed extends Wrapper[Reversed] { + def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga + def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa + implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] = + o.reverse + } + + opaque type Unordered[A] = A + object Unordered extends Wrapper[Unordered] { + def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga + def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa + implicit def unorderedOrdering[A]: Ordering[Unordered[A]] = + Ordering.by(_ => ()) + } +} \ No newline at end of file diff --git a/tests/pos/opaque-nullable.scala b/tests/pos/opaque-nullable.scala new file mode 100644 index 000000000000..ce32d7c1cfed --- /dev/null +++ b/tests/pos/opaque-nullable.scala @@ -0,0 +1,26 @@ +object nullable { + opaque type Nullable[A >: Null <: AnyRef] = A + + object Nullable { + def apply[A >: Null <: AnyRef](a: A): Nullable[A] = a + + implicit class NullableOps[A >: Null <: AnyRef](na: Nullable[A]) { + def exists(p: A => Boolean): Boolean = + na != null && p(na) + def filter(p: A => Boolean): Nullable[A] = + if (na != null && p(na)) na else null + def flatMap[B >: Null <: AnyRef](f: A => Nullable[B]): Nullable[B] = + if (na == null) null else f(na) + def forall(p: A => Boolean): Boolean = + na == null || p(na) + def getOrElse(a: => A): A = + if (na == null) a else na + def map[B >: Null <: AnyRef](f: A => B): Nullable[B] = + if (na == null) null else f(na) + def orElse(na2: => Nullable[A]): Nullable[A] = + if (na == null) na2 else na + def toOption: Option[A] = + Option(na) + } + } +} \ No newline at end of file diff --git a/tests/pos/opaque-propability.scala b/tests/pos/opaque-propability.scala new file mode 100644 index 000000000000..68048949ea93 --- /dev/null +++ b/tests/pos/opaque-propability.scala @@ -0,0 +1,43 @@ +object prob { + opaque type Probability = Double + + object Probability { + def apply(n: Double): Option[Probability] = + if (0.0 <= n && n <= 1.0) Some(n) else None + + def unsafe(p: Double): Probability = { + require(0.0 <= p && p <= 1.0, s"probabilities lie in [0, 1] (got $p)") + p + } + + def asDouble(p: Probability): Double = p + + val Never: Probability = 0.0 + val CoinToss: Probability = 0.5 + val Certain: Probability = 1.0 + + implicit val ordering: Ordering[Probability] = + implicitly[Ordering[Double]] + + implicit class ProbabilityOps(p1: Probability) extends AnyVal { + def unary_~ : Probability = Certain - p1 + def &(p2: Probability): Probability = p1 * p2 + def |(p2: Probability): Probability = p1 + p2 - (p1 * p2) + + def isImpossible: Boolean = p1 == Never + def isCertain: Boolean = p1 == Certain + + import scala.util.Random + + def sample(r: Random = Random): Boolean = r.nextDouble <= p1 + def toDouble: Double = p1 + } + + val caughtTrain = Probability.unsafe(0.3) + val missedTrain = ~caughtTrain + val caughtCab = Probability.CoinToss + val arrived = caughtTrain | (missedTrain & caughtCab) + + println((1 to 5).map(_ => arrived.sample()).toList) + } +} From 6fee4bb5e9c80ec6fba013edf3059d0ece438c6b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 11:13:31 +0100 Subject: [PATCH 20/54] Consider GADT bounds for findMember and underlying The `findMember` and `underlying` methods on `TermRef` now take GADT bounds into account. This replaces the previous special case in `secectionType`. The previous case did always pass -Ycheck. Currently, the treatment is restricted to TypeRefs of opaque types. We should extend that to all GADT-bound TypeRefs. The question is how this will affect performance. --- .../src/dotty/tools/dotc/core/Types.scala | 18 ++++++++--- .../dotty/tools/dotc/typer/TypeAssigner.scala | 30 ++++++++----------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c3e50d8ff0ba..ecaf90556d8d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -509,8 +509,12 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => d.findMember(name, pre, excluded) - case d => go(d.info) + case d: ClassDenotation => + d.findMember(name, pre, excluded) + case d => + val mbr = go(d.info) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) } case tp: AppliedType => tp.tycon match { @@ -521,7 +525,7 @@ object Types { case _ => go(tp.superType) } - case tp: ThisType => // ??? inline + case tp: ThisType => goThis(tp) case tp: RefinedType => if (name eq tp.refinedName) goRefined(tp) else go(tp.parent) @@ -2133,7 +2137,13 @@ object Types { override def designator = myDesignator override protected def designator_=(d: Designator) = myDesignator = d - override def underlying(implicit ctx: Context): Type = info + override def underlying(implicit ctx: Context): Type = { + if (symbol.is(Opaque)) { + val gadtBounds = ctx.gadt.bounds(symbol) + if (gadtBounds != null) return gadtBounds + } + info + } } final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b3dd9cfbd9e4..081c8c2130ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -233,25 +233,21 @@ trait TypeAssigner { */ def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { val mbr = site.member(name) - if (reallyExists(mbr)) site.select(name, mbr) - else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { + if (reallyExists(mbr)) + site.select(name, mbr) + else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) TryDynamicCallType - } + else if (site.isErroneous || name.toTermName == nme.ERROR) + UnspecifiedErrorType else { - val site1 = site.followGADT - if (site1.exists) selectionType(site1, name, pos) - else if (site.isErroneous || name.toTermName == nme.ERROR) - UnspecifiedErrorType - else { - def kind = if (name.isTypeName) "type" else "value" - def addendum = - if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" - else "" - errorType( - if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" - else NotAMember(site, name, kind), - pos) - } + def kind = if (name.isTypeName) "type" else "value" + def addendum = + if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" + else "" + errorType( + if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" + else NotAMember(site, name, kind), + pos) } } From ea65c25dbc69543236e245eb7594bbc86338625e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 11:14:31 +0100 Subject: [PATCH 21/54] Extend opaque companion context to inlined code The GADT bounds set in an opaque companion also need to be established for any inlined code coming from the companion. Test case in opaque-immutable-array.scala. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 7 ++- .../src/dotty/tools/dotc/typer/Namer.scala | 30 ++++++++----- ...opaque-goups.scala => opaque-groups.scala} | 0 tests/pos/opaque-immutable-array.scala | 43 +++++++++++++++++++ 4 files changed, 68 insertions(+), 12 deletions(-) rename tests/pos/{opaque-goups.scala => opaque-groups.scala} (100%) create mode 100644 tests/pos/opaque-immutable-array.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a3be000dbbc2..e4f1a96e63a4 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1017,8 +1017,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] - override def inlineContext(call: Tree)(implicit ctx: Context): Context = - ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + override def inlineContext(call: Tree)(implicit ctx: Context): Context = { + val ictx = ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + def stopAt(owner: Symbol) = owner.is(Package) || ctx.owner.isContainedIn(owner) + (ictx /: call.symbol.ownersIterator.takeWhile(!stopAt(_)))(ctx.handleOpaqueCompanion) + } /** All enclosing calls that are currently inlined, from innermost to outermost */ def enclosingInlineds(implicit ctx: Context): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fe605981317f..dae5f9b61f49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -101,22 +101,32 @@ trait NamerContextOps { this: Context => if (owner.exists) freshCtx.setOwner(owner) else freshCtx } + /** If `owner` is a companion object of an opaque type, record the alias + * in the GADT bounds of `freshCtx. + */ + def handleOpaqueCompanion(freshCtx: FreshContext, owner: Symbol): FreshContext = { + if (owner.is(Module)) { + val opaq = owner.companionOpaqueType + val alias = opaq.opaqueAlias + if (alias.exists) { + println(i"set GADT bounds of $opaq : $alias") + val result = freshCtx.setFreshGADTBounds + result.gadt.setBounds(opaq, TypeAlias(alias)) + result + } + else freshCtx + } + else freshCtx + } + /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - var localCtx: FreshContext = ctx.fresh.setNewScope + val localCtx: FreshContext = ctx.fresh.setNewScope selfInfo match { case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => localCtx.scope.openForMutations.enter(sym) case _ => } - if (ctx.owner.is(Module)) { - val opaq = ctx.owner.companionOpaqueType - val alias = opaq.opaqueAlias - if (alias.exists) { - localCtx = localCtx.setFreshGADTBounds - localCtx.gadt.setBounds(opaq, TypeAlias(alias)) - } - } - localCtx + handleOpaqueCompanion(localCtx, ctx.owner) } def packageContext(tree: untpd.PackageDef, pkg: Symbol): Context = diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-groups.scala similarity index 100% rename from tests/pos/opaque-goups.scala rename to tests/pos/opaque-groups.scala diff --git a/tests/pos/opaque-immutable-array.scala b/tests/pos/opaque-immutable-array.scala new file mode 100644 index 000000000000..ad657ff17a10 --- /dev/null +++ b/tests/pos/opaque-immutable-array.scala @@ -0,0 +1,43 @@ +object ia { + + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + object IArray { + @inline final def initialize[A](body: => Array[A]): IArray[A] = body + + @inline final def size[A](ia: IArray[A]): Int = ia.length + @inline final def get[A](ia: IArray[A], i: Int): A = ia(i) + + // return a sorted copy of the array + def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = { + val arr = Arrays.copyOf(ia, ia.length) + scala.util.Sorting.quickSort(arr) + arr + } + + // use a standard java method to search a sorted IArray. + // (note that this doesn't mutate the array). + def binarySearch(ia: IArray[Long], elem: Long): Int = + Arrays.binarySearch(ia, elem) + } + + // same as IArray.binarySearch but implemented by-hand. + // + // given a sorted IArray, returns index of `elem`, + // or a negative value if not found. + def binaryIndexOf(ia: IArray[Long], elem: Long): Int = { + var lower: Int = 0 + var upper: Int = IArray.size(ia) + while (lower <= upper) { + val middle = (lower + upper) >>> 1 + val n = IArray.get(ia, middle) + + if (n == elem) return middle + else if (n < elem) lower = middle + 1 + else upper = middle - 1 + } + -lower - 1 + } +} \ No newline at end of file From e50e66944100626030a83d11c6609be260f9cbb2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 21:11:36 +0100 Subject: [PATCH 22/54] Remove stray println --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index dae5f9b61f49..089b359ada03 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -109,7 +109,6 @@ trait NamerContextOps { this: Context => val opaq = owner.companionOpaqueType val alias = opaq.opaqueAlias if (alias.exists) { - println(i"set GADT bounds of $opaq : $alias") val result = freshCtx.setFreshGADTBounds result.gadt.setBounds(opaq, TypeAlias(alias)) result From e3e58eb26ee69bd90e4df4c7d800ba18aa5c9254 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 18:03:28 +0100 Subject: [PATCH 23/54] Disable failing FromTasty tests --- compiler/test/dotty/tools/dotc/FromTastyTests.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 75f2ed603242..695e6878a0f5 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -28,6 +28,11 @@ class FromTastyTests extends ParallelTesting { implicit val testGroup: TestGroup = TestGroup("posTestFromTasty") val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( + + "opaque.scala", + "opaque-propability.scala", + "opaque-immutable-array.scala", + "macro-deprecate-dont-touch-backquotedidents.scala", "t247.scala", From 252f04ae60d29b1bf8778f6e91ada00d5a5cd7ea Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 14:04:41 +0100 Subject: [PATCH 24/54] Fix ctx of opaque type companion --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 3 +++ compiler/test/dotty/tools/dotc/FromTastyTests.scala | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 29096e44bc38..a755f9130e1c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1244,6 +1244,8 @@ object Trees { Stats.record("TreeAccumulator.foldOver total") def localCtx = if (tree.hasType && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx + def templateCtx = + ctx.handleOpaqueCompanion(ctx.fresh, ctx.owner) tree match { case Ident(name) => x @@ -1320,6 +1322,7 @@ object Trees { implicit val ctx = localCtx this(x, rhs) case tree @ Template(constr, parents, self, _) => + implicit val ctx = templateCtx this(this(this(this(x, constr), parents), self), tree.body) case Import(expr, selectors) => this(x, expr) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 695e6878a0f5..d199a80243c3 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -29,8 +29,6 @@ class FromTastyTests extends ParallelTesting { val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( - "opaque.scala", - "opaque-propability.scala", "opaque-immutable-array.scala", "macro-deprecate-dont-touch-backquotedidents.scala", From c8ff1d66e1d39099c19c7e0777a1385c5754ffff Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 15:35:10 +0100 Subject: [PATCH 25/54] Fix ctx and call of inlined opaque extension methods --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 5 +++-- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 5 ++++- compiler/test/dotty/tools/dotc/FromTastyTests.scala | 2 -- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index ef092898efa3..c4ab9ca41efb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -986,7 +986,7 @@ class TreeUnpickler(reader: TastyReader, def readLengthTerm(): Tree = { val end = readEnd() - def readBlock(mkTree: (List[Tree], Tree) => Tree): Tree = { + def readBlock(mkTree: (List[Tree], Tree) => Tree)(implicit ctx: Context): Tree = { val exprReader = fork skipTree() val stats = readStats(ctx.owner, end) @@ -1015,7 +1015,8 @@ class TreeUnpickler(reader: TastyReader, readBlock(Block) case INLINED => val call = readTerm() - readBlock((defs, expr) => Inlined(call, defs.asInstanceOf[List[MemberDef]], expr)) + val inlineCtx = tpd.inlineContext(call) + readBlock((defs, expr) => Inlined(call, defs.asInstanceOf[List[MemberDef]], expr))(inlineCtx) case IF => If(readTerm(), readTerm(), readTerm()) case LAMBDA => diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 103afdad960a..c241a50fa563 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -238,9 +238,12 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase // In the case of macros we keep the call to be able to reconstruct the parameters that // are passed to the macro. This same simplification is applied in ReifiedQuotes when the // macro splices are evaluated. + def symTrace = + if (call.symbol.owner.companionOpaqueType.exists) call.symbol.owner + else call.symbol.topLevelClass val callTrace = if (call.symbol.is(Macro)) call - else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + else Ident(symTrace.typeRef).withPos(call.pos) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index d199a80243c3..00cf744b10e8 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -29,8 +29,6 @@ class FromTastyTests extends ParallelTesting { val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( - "opaque-immutable-array.scala", - "macro-deprecate-dont-touch-backquotedidents.scala", "t247.scala", From cf740ba4c7e379de0f1dd60f4e7920d0c2229c6a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 14:01:09 +0200 Subject: [PATCH 26/54] Fix rebase breakage --- .../src/dotty/tools/dotc/core/Types.scala | 13 +-- .../tools/dotc/transform/FirstTransform.scala | 2 - .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- tests/pos/opaque-goups.scala | 95 +++++++++++++++++++ 4 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 tests/pos/opaque-goups.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ecaf90556d8d..ef5b321a8fa9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -509,12 +509,8 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => - d.findMember(name, pre, excluded) - case d => - val mbr = go(d.info) - if (mbr.exists) mbr - else followGADT.findMember(name, pre, excluded) + case d: ClassDenotation => d.findMember(name, pre, excluded) + case d => goTypeRef(d) } case tp: AppliedType => tp.tycon match { @@ -554,6 +550,11 @@ object Types { case _ => NoDenotation } + def goTypeRef(d: Denotation) = { + val mbr = go(d.info) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) + } def goRec(tp: RecType) = if (tp.parent == null) NoDenotation else { diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index b8e0819471fb..6671e81c0e81 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -77,8 +77,6 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => else sym } - override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 26d6176d4551..bd2982dbaf72 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -131,7 +131,9 @@ object ProtoTypes { // Note: can't use `m.info` here because if `m` is a method, `m.info` // loses knowledge about `m`'s default arguments. || mbr match { // hasAltWith inlined for performance - case NoDenotation => tp1.exists && isMatchedBy(tp1.followGADT) + case NoDenotation => + val tp2 = tp1.followGADT + tp2.exists && isMatchedBy(tp2) case mbr: SingleDenotation => mbr.exists && qualifies(mbr) case _ => mbr hasAltWith qualifies } diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-goups.scala new file mode 100644 index 000000000000..4391d756494b --- /dev/null +++ b/tests/pos/opaque-goups.scala @@ -0,0 +1,95 @@ +package object groups { + trait Semigroup[A] { + def combine(x: A, y: A): A + } + + object Semigroup { + def instance[A](f: (A, A) => A): Semigroup[A] = + new Semigroup[A] { + def combine(x: A, y: A): A = f(x, y) + } + } + + type Id[A] = A + + trait Wrapping[F[_]] { + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](ga: G[F[A]]): G[A] + } + + abstract class Wrapper[F[_]] { self => + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](gfa: G[F[A]]): G[A] + + final def apply[A](a: A): F[A] = wraps[Id, A](a) + + implicit object WrapperWrapping extends Wrapping[F] { + def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga) + def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga) + } + } + + opaque type First[A] = A + object First extends Wrapper[First] { + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga + def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa + implicit def firstSemigroup[A]: Semigroup[First[A]] = + Semigroup.instance((x, y) => x) + } + + opaque type Last[A] = A + object Last extends Wrapper[Last] { + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } + + opaque type Min[A] = A + object Min extends Wrapper[Min] { + def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga + def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa + implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] = + Semigroup.instance(o.min) + } + + opaque type Max[A] = A + object Max extends Wrapper[Max] { + def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga + def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa + implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] = + Semigroup.instance(o.max) + } + + opaque type Plus[A] = A + object Plus extends Wrapper[Plus] { + def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga + def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa + implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] = + Semigroup.instance(n.plus) + } + + opaque type Times[A] = A + object Times extends Wrapper[Times] { + def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga + def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa + implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] = + Semigroup.instance(n.times) + } + + opaque type Reversed[A] = A + object Reversed extends Wrapper[Reversed] { + def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga + def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa + implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] = + o.reverse + } + + opaque type Unordered[A] = A + object Unordered extends Wrapper[Unordered] { + def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga + def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa + implicit def unorderedOrdering[A]: Ordering[Unordered[A]] = + Ordering.by(_ => ()) + } +} \ No newline at end of file From b15866639048500f6d53e0e0c1cb0d917072b599 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 15:56:28 +0200 Subject: [PATCH 27/54] Fix modifier printing --- .../src/dotty/tools/dotc/core/Flags.scala | 17 ++++++---- .../dotc/printing/DecompilerPrinter.scala | 6 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 31 +++++++++++-------- .../tools/dottydoc/model/factories.scala | 2 +- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index c9e2f1926fb6..ffaf821528e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -444,15 +444,20 @@ object Flags { // --------- Combined Flag Sets and Conjunctions ---------------------- /** Flags representing source modifiers */ - final val SourceModifierFlags = - commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased, Opaque) + private val CommonSourceModifierFlags = + commonFlags(Private, Protected, Final, Case, Implicit, Override, JavaStatic) + + final val TypeSourceModifierFlags = + CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque + + final val TermSourceModifierFlags = + CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local - // | Mutable is subsumed by commonFlags(Opaque) from SourceModifierFlags - // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags + TypeSourceModifierFlags.toCommonFlags | + TermSourceModifierFlags.toCommonFlags | + commonFlags(Module, Param, Synthetic, Package, Local, Mutable, Trait) assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index c590adf2206e..a398ab652aa5 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -35,8 +35,8 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def templateText(tree: TypeDef, impl: Template): Text = { val decl = - if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) - else modText(tree.mods &~ (Final | Module), keywordStr("object")) + if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) + else modText(tree.mods &~ (Final | Module), keywordStr("object"), isType = false) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ "" } @@ -44,7 +44,7 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { import untpd.{modsDeco => _, _} dclTextOr(tree) { val printLambda = tree.symbol.isAnonymousFunction - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) provided (!printLambda) + val prefix = modText(tree.mods, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) provided (!printLambda) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt).provided(!printLambda) ~ optText(tree.rhs)((if (printLambda) " => " else " = ") ~ _) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48bc992f8a47..ee9a1df5921c 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -75,7 +75,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override protected def recursionLimitExceeded() = {} - protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags + protected def PrintableFlags(isType: Boolean) = { + if (isType) TypeSourceModifierFlags | Module | Local + else TermSourceModifierFlags | Label | Module | Local + }.toCommonFlags override def nameString(name: Name): String = if (ctx.settings.YdebugNames.value) name.debugString else name.toString @@ -403,7 +406,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree @ TypeDef(name, rhs) => def typeDefText(tparamsText: => Text, rhsText: => Text) = dclTextOr(tree) { - modText(tree.mods, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ + modText(tree.mods, keywordStr("type"), isType = true) ~~ + (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { @@ -441,7 +445,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { - modText(tree.mods, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) + modText(tree.mods, keywordStr("object"), isType = false) ~~ + nameIdText(tree) ~ toTextTemplate(impl) } case SymbolLit(str) => "'" + str @@ -498,8 +503,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { t ~ cxBoundToText(cxb) } case PatDef(mods, pats, tpt, rhs) => - modText(mods, keywordStr("val")) ~~ toText(pats, ", ") ~ optAscription(tpt) ~ - optText(rhs)(" = " ~ _) + modText(mods, keywordStr("val"), isType = false) ~~ + toText(pats, ", ") ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) case ParsedTry(expr, handler, finalizer) => changePrec(GlobalPrec) { keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _) @@ -606,7 +611,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ + modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } } @@ -615,7 +620,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + val prefix = modText(tree.mods, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ optText(tree.rhs)(" = " ~ _) @@ -630,7 +635,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val prefix: Text = if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt else { - var modsText = modText(constr.mods, "") + var modsText = modText(constr.mods, "", isType = false) if (!modsText.isEmpty) modsText = " " ~ modsText if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } @@ -658,7 +663,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def templateText(tree: TypeDef, impl: Template): Text = { - val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") } @@ -681,12 +686,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + protected def modText(mods: untpd.Modifiers, kw: String, isType: Boolean): Text = { // DD val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param var flagMask = if (ctx.settings.YdebugFlags.value) AnyFlags - else if (suppressKw) PrintableFlags &~ Private - else PrintableFlags + else if (suppressKw) PrintableFlags(isType) &~ Private + else PrintableFlags(isType) if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes val flags = mods.flags & flagMask val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) @@ -755,7 +760,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else { var flags = sym.flagsUNSAFE if (flags is TypeParam) flags = flags &~ Protected - Text((flags & PrintableFlags).flagStrings map (flag => stringToText(keywordStr(flag))), " ") + Text((flags & PrintableFlags(sym.isType)).flagStrings map (flag => stringToText(keywordStr(flag))), " ") } override def toText(denot: Denotation): Text = denot match { diff --git a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala index 66f42eb6e740..a9bcf8d738eb 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala @@ -26,7 +26,7 @@ object factories { type TypeTree = dotty.tools.dotc.ast.Trees.Tree[Type] def flags(t: Tree)(implicit ctx: Context): List[String] = - (t.symbol.flags & SourceModifierFlags) + (t.symbol.flags & (if (t.symbol.isType) TypeSourceModifierFlags else TermSourceModifierFlags)) .flagStrings.toList .filter(_ != "") .filter(_ != "interface") From 22a71672f402b89c0c5eb5e7ef4dfa8183814802 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 15:47:22 +0100 Subject: [PATCH 28/54] Schema for named extensions Following suggestions by @sjrd, a worked out scheme to have named extensions, using extension for { ... } syntax. --- docs/docs/internals/syntax.md | 14 +- .../reference/extend/extension-methods.md | 146 ++++++++++++++++++ .../reference/extend/instance-declarations.md | 62 ++++++++ docs/docs/reference/extend/translation.md | 85 ++++++++++ 4 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 docs/docs/reference/extend/extension-methods.md create mode 100644 docs/docs/reference/extend/instance-declarations.md create mode 100644 docs/docs/reference/extend/translation.md diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 3c9d9ef1c6c7..2a16fc06e323 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -330,12 +330,18 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef -ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) +ClassDef ::= id ClassConstr [TemplateClause] ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] -ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor -EnumDef ::= id ClassConstr [‘extends’ [ConstrApps]] EnumBody EnumDef(mods, name, tparams, template) -TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] +ObjectDef ::= id [TemplateClause] ModuleDef(mods, name, template) // no constructor +EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody TypeDef(mods, name, template) +Extension ::= 'extension' id [ExtensionParams] Extension(name, templ) + 'for' Type ExtensionClause +ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] +ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + +TemplateClause ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md new file mode 100644 index 000000000000..1790f89b6e45 --- /dev/null +++ b/docs/docs/reference/extend/extension-methods.md @@ -0,0 +1,146 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +extension CircleOps for Circle { + def circumference: Double = this.radius * math.Pi * 2 +} +``` + +The extension adds a method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Meaning of `this` + +Inside an extension method, the name `this` stands for the receiver on which the +method is applied when it is invoked. E.g. in the application of `circle.circumference`, +the `this` in the body of `circumference` refers to `circle`. As usual, `this` can be elided, so we could also have defined `CircleOps` like this: + +```scala +extension CircleOps for Circle { + def circumference: Double = radius * math.Pi * 2 +} +``` + +### Scope of Extensions + +Extensions can appear anywhere in a program; there is no need to co-define them with the types they extend. Extension methods are available wherever their defining extension is in scope. Extensions can be inherited or imported like normal definitions. + +### Extended Types + +An extension can add methods to arbitrary types. For instance, the following +clause adds a `longestStrings` extension method to a `Seq[String]`: + +```scala +extension StringOps for Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = map(_.length).max + filter(_.length == maxLength) + } +} +``` + +### Generic Extensions + +The previous example extended a specific instance of a generic type. It is also possible +to extend a generic type by adding type parameters to an extension: + +```scala +extension ListOps[T] for List[T] { + def second: T = tail.head +} +``` + +or: + + +```scala +extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +### Bounded Generic Extensions + +It is possible to use bounds for the type parameters of an extension. But subtype and context bounds are supported: + +```scala +extension ShapeListOps[T <: Shape] for List[T] { + def totalArea = map(_.area).sum +} + +extension OrdSeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 +} +``` + +### Implicit Parameters for Type Patterns + +The standard translation of context bounds expands the bound in the last example to an implicit _evidence_ parameter of type `math.Ordering[T]`. It is also possible to give evidence parameters explicitly. The following example is equivalent to the previous one: + +```scala +extension OrdSeqOps[T](implicit ev: math.Ordering[T]) for Seq[T] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +There can be only one parameter clause following a type pattern and it must be implicit. As usual, one can combine context bounds and implicit evidence parameters. + +### Toplevel Type Variables + +A type pattern consisting of a top-level typevariable introduces a fully generic extension. For instance, the following extension introduces `x ~ y` as an alias +for `(x, y)`: + +```scala +extension InfixPair[T] for T { + def ~ [U](that: U) = (this, that) +} +``` + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + extenson Ensuring[T] for T { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } +} +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md new file mode 100644 index 000000000000..2ab5d3883c11 --- /dev/null +++ b/docs/docs/reference/extend/instance-declarations.md @@ -0,0 +1,62 @@ +--- +layout: doc-page +title: "Instance Declarations" +--- + +In addition to adding methods, an extension can also implement traits. Extensions implementing traits are also called _instance declarations_. For example, + +```scala +trait HasArea { + def area: Double +} + +extension CircleHasArea for Circle : HasArea { + def area = this.radius * this.radius * math.Pi +} +``` + +This extension makes `Circle` an instance of the `HasArea` trait. Specifically, it defines an implicit subclass of `HasArea` +which takes a `Circle` as argument and provides the given implementation. Hence, the implementation of the extension above would be like this + +```scala +implicit class CircleHasArea($this: Circle) extends HasArea { + def area = $this.radius * $this.radius * math.Pi +} +``` + +An instance definition can thus provide a kind of "implements" relationship that can be defined independently of the types it connects. + +### Generic Instance Declarations + +Just like extension methods, instance declarations can also be generic and their type parameters can have bounds. + +For example, assume we have the following two traits, which define binary and unary (infix) equality tests: + +```scala +trait Eql[T] { + def eql (x: T, y: T): Boolean +} + +trait HasEql[T] { + def === (that: T): Boolean +} +``` + +The following extension makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: + +```scala +extension HasEqlImpl[T : Eql] for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +### Syntax of Extensions + +The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + + TmplDef ::= ... + | ‘extension’ ExtensionDef + ExtensionDef ::= id [ExtensionParams] 'for' Type ExtensionClause + ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md new file mode 100644 index 000000000000..0a248018cc3e --- /dev/null +++ b/docs/docs/reference/extend/translation.md @@ -0,0 +1,85 @@ +--- +layout: doc-page +title: "Translation of Extensions" +--- + +Extensons are closely related to implicit classes and can be translated into them. In short, +an extension that just adds extension methods translates into an implicit value class whereas an instance declaration translates into a regular implicit class. The following sections sketch this translation. + +Conversely, it is conceivable (and desirable) to replace most usages of implicit classes and value classes by extensions and [opaque types](../opaques.html). We plan to [drop](../dropped/implicit-value-classes.html) +these constructs in future versions of the language. Once that is achieved, the translations described +below can be simply composed with the existing translations of implicit and value classes into the core language. It is +not necessary to retain implicit and value classes as an intermediate step. + + +### Translation of Extension Methods + +Assume an extension + + extension for { } + +where both `` and `` can be absent. +For simplicity assume that there are no context bounds on any of the type parameters +in ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. + +The extension is translated to the following implicit value class: + + implicit class (private val $this: ) extends AnyVal { + import $this._ + + } + +Here `` results from `` by augmenting any definition in with the parameters and replacing any occurrence of `this` with `$this`. + +For example, the extension + +```scala +extension SeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class SeqOps[T](private val $this: List[T]) extends AnyVal { + import $this._ + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Instance Declarations + +Now, assume an extension + + extension if for : { } + +where ``, `` and `` are as before. +This extension is translated to + + implicit class ($this: ) extends { + import $this._ + + } + +As before, `` is computed from `` by replacing any occurrence of `this` with `$this`. However, all parameters in now stay on the class definition, instead of being distributed to all members in ``. This is necessary in general, since `` might contain value definitions or other statements that cannot be +parameterized. + +For example, the extension + +```scala +extension HasEqlImpl[T : Eql) for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +would be translated to + +```scala +implicit class HasEqlForEql[T]($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + import $this._ + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) +} +``` From 3effb9631d25f7971083cce0205cee19c25f73ac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 16:46:36 +0100 Subject: [PATCH 29/54] Parser for fixed syntax --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 5 ++ .../dotty/tools/dotc/parsing/Parsers.scala | 83 +++++++++++++++---- .../src/dotty/tools/dotc/parsing/Tokens.scala | 5 +- docs/docs/internals/syntax.md | 4 +- 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 3f2136b8ce17..7e5982db3b7d 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,6 +40,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } + /** extend extended impl */ + case class Extension(name: TypeName, extended: Tree, impl: Template) extends DefTree + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -139,6 +142,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Enum() extends Mod(Flags.EmptyFlags) case class EnumCase() extends Mod(Flags.EmptyFlags) + + case class InstanceDecl() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ce7e56b1cd31..b6e3f4fd4aa9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1058,7 +1058,7 @@ object Parsers { * | PostfixExpr `match' `{' CaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] - * Ascription ::= `:' CompoundType + * Ascription ::= `:' InfixType * | `:' Annotation {Annotation} * | `:' `_' `*' */ @@ -1178,6 +1178,13 @@ object Parsers { t } + /** Ascription ::= `:' InfixType + * | `:' Annotation {Annotation} + * | `:' `_' `*' + * PatternAscription ::= `:' TypePattern + * | `:' `_' `*' + * TypePattern ::= RefinedType + */ def ascription(t: Tree, location: Location.Value) = atPos(startOffset(t)) { in.skipToken() in.token match { @@ -1539,7 +1546,7 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() } else Nil - /** Pattern1 ::= PatVar Ascription + /** Pattern1 ::= PatVar PatternAscription * | Pattern2 */ def pattern1(): Tree = { @@ -1831,8 +1838,10 @@ object Parsers { * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] + * ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’) + * ImplicitMods ::= `implicit` [`unused`] | `unused` `implicit` */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { + def paramClauses(owner: Name, ofCaseClass: Boolean = false, ofExtension: Boolean = false): List[List[ValDef]] = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once var firstClauseOfCaseClass = ofCaseClass @@ -1878,7 +1887,7 @@ object Parsers { } } def paramClause(): List[ValDef] = inParens { - if (in.token == RPAREN) Nil + if (!ofExtension && in.token == RPAREN) Nil else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -1891,7 +1900,8 @@ object Parsers { } } funArgMods() - + if (ofExtension && !imods.is(Implicit)) + syntaxError(i"parameters of extension must be implicit") commaSeparated(() => param()) } } @@ -1901,7 +1911,7 @@ object Parsers { imods = EmptyModifiers paramClause() :: { firstClauseOfCaseClass = false - if (imods is Implicit) Nil else clauses() + if (imods.is(Implicit) || ofExtension) Nil else clauses() } } else Nil } @@ -2159,6 +2169,7 @@ object Parsers { /** TmplDef ::= ([`case'] ‘class’ | trait’) ClassDef * | [`case'] `object' ObjectDef * | `enum' EnumDef + * | `extension' ExtensionDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { in.token match { @@ -2174,13 +2185,15 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => enumDef(start, mods, atPos(in.skipToken()) { Mod.Enum() }) + case EXTENSION => + extensionDef(start, posMods(start, mods)) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) EmptyTree } } - /** ClassDef ::= id ClassConstr TemplateOpt + /** ClassDef ::= id ClassConstr [TemplateClause] */ def classDef(start: Offset, mods: Modifiers): TypeDef = atPos(start, nameStart) { classDefRest(start, mods, ident().toTypeName) @@ -2188,7 +2201,7 @@ object Parsers { def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = { val constr = classConstr(name, isCaseClass = mods is Case) - val templ = templateOpt(constr) + val templ = templateClauseOpt(constr) TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start)) } @@ -2197,7 +2210,7 @@ object Parsers { def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val vparamss = paramClauses(owner, ofCaseClass = isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } @@ -2206,14 +2219,14 @@ object Parsers { def constrModsOpt(owner: Name): Modifiers = modifiers(accessModifierTokens, annotsAsMods()) - /** ObjectDef ::= id TemplateOpt + /** ObjectDef ::= id [TemplateClause] */ def objectDef(start: Offset, mods: Modifiers): ModuleDef = atPos(start, nameStart) { objectDefRest(start, mods, ident()) } def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = { - val template = templateOpt(emptyConstructor) + val template = templateClauseOpt(emptyConstructor) ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) } @@ -2223,7 +2236,7 @@ object Parsers { val modName = ident() val clsName = modName.toTypeName val constr = classConstr(clsName) - val impl = templateOpt(constr, isEnum = true) + val impl = templateClauseOpt(constr, isEnum = true, bodyRequired = true) TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) } @@ -2269,6 +2282,36 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } + /** ExtensionDef ::= id [ExtensionParams] 'for' AnnotType ExtensionClause + * ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + * ExtensionClause ::= [`:` Template] + * | [nl] `{` `def` DefDef {semi `def` DefDef} `}` + */ + def extensionDef(start: Offset, mods: Modifiers): Extension = atPos(start, nameStart) { + val name = ident().toTypeName + val tparams = typeParamClauseOpt(ParamOwner.Class) + val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) + val constr = makeConstructor(Nil, vparamss) + accept(FOR) + val extended = annotType() + val templ = + if (in.token == COLON) { + in.nextToken() + template(constr, bodyRequired = true)._1 + } + else { + val templ = templateClauseOpt(constr, bodyRequired = true) + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + templ + } + Extension(name, extended, templ) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2285,26 +2328,30 @@ object Parsers { * @return a pair consisting of the template, and a boolean which indicates * whether the template misses a body (i.e. no {...} part). */ - def template(constr: DefDef, isEnum: Boolean = false): (Template, Boolean) = { + def template(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): (Template, Boolean) = { newLineOptWhenFollowedBy(LBRACE) if (in.token == LBRACE) (templateBodyOpt(constr, Nil, isEnum), false) else { val parents = tokenSeparated(WITH, constrApp) newLineOptWhenFollowedBy(LBRACE) - if (isEnum && in.token != LBRACE) + if (bodyRequired && in.token != LBRACE) syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) val missingBody = in.token != LBRACE (templateBodyOpt(constr, parents, isEnum), missingBody) } } - /** TemplateOpt = [`extends' Template | TemplateBody] + /** TemplateClause = `extends' Template | TemplateBody + * TemplateClauseOpt = [TemplateClause] */ - def templateOpt(constr: DefDef, isEnum: Boolean = false): Template = - if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum)._1 } + def templateClauseOpt(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): Template = + if (in.token == EXTENDS) { + in.nextToken() + template(constr, isEnum, bodyRequired)._1 + } else { newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) template(constr, isEnum)._1 + if (in.token == LBRACE || bodyRequired) template(constr, isEnum, bodyRequired)._1 else Template(constr, Nil, EmptyValDef, Nil) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 5becc9161309..e9e692d70c12 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -179,6 +179,7 @@ object Tokens extends TokensCommon { final val ENUM = 63; enter(ENUM, "enum") final val ERASED = 64; enter(ERASED, "erased") final val OPAQUE = 65; enter(OPAQUE, "opaque") + final val EXTENSION = 66; enter(EXTENSION, "extension") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -199,7 +200,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, OPAQUE) + final val alphaKeywords = tokenRange(IF, EXTENSION) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -220,7 +221,7 @@ object Tokens extends TokensCommon { final val canStartBindingTokens = identifierTokens | BitSet(USCORE, LPAREN) - final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) + final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, EXTENSION, CASECLASS, CASEOBJECT) final val dclIntroTokens = BitSet(DEF, VAL, VAR, TYPE) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 2a16fc06e323..c7b8e622a726 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -330,13 +330,13 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef + | ‘extension’ ExtensionDef ClassDef ::= id ClassConstr [TemplateClause] ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [TemplateClause] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody TypeDef(mods, name, template) -Extension ::= 'extension' id [ExtensionParams] Extension(name, templ) - 'for' Type ExtensionClause +ExtensionDef ::= id [ExtensionParams] 'for' AnnotType ExtensionClause Extension(name, type, templ) ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] ExtensionClause ::= [`:` Template] | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ From a8d48e91de51bb6ad5309e0987b5528563be249f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 18:04:04 +0100 Subject: [PATCH 30/54] Desugarings for extensions --- .../src/dotty/tools/dotc/ast/Desugar.scala | 106 +++++++++++++++--- compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 +- .../dotty/tools/dotc/config/Printers.scala | 1 + 3 files changed, 94 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a9999c58f81e..8ed5398ef5f4 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -11,6 +11,7 @@ import language.higherKinds import typer.FrontEnd import collection.mutable.ListBuffer import util.Property +import config.Printers.desugr import reporting.diagnostic.messages._ import reporting.trace @@ -154,6 +155,25 @@ object desugar { ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit) } + private def desugarTypeBindings( + bindings: List[TypeDef], + forPrimaryConstructor: Boolean = false)(implicit ctx: Context): (List[TypeDef], List[ValDef]) = { + val epbuf = new ListBuffer[ValDef] + def desugarContextBounds(rhs: Tree): Tree = rhs match { + case ContextBounds(tbounds, cxbounds) => + epbuf ++= makeImplicitParameters(cxbounds, forPrimaryConstructor) + tbounds + case LambdaTypeTree(tparams, body) => + cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) + case _ => + rhs + } + val bindings1 = bindings mapConserve { tparam => + cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) + } + (bindings1, epbuf.toList) + } + /** Expand context bounds to evidence params. E.g., * * def f[T >: L <: H : B](params) @@ -171,21 +191,8 @@ object desugar { private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = { val DefDef(name, tparams, vparamss, tpt, rhs) = meth val mods = meth.mods - val epbuf = new ListBuffer[ValDef] - def desugarContextBounds(rhs: Tree): Tree = rhs match { - case ContextBounds(tbounds, cxbounds) => - epbuf ++= makeImplicitParameters(cxbounds, isPrimaryConstructor) - tbounds - case LambdaTypeTree(tparams, body) => - cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) - case _ => - rhs - } - val tparams1 = tparams mapConserve { tparam => - cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) - } - - val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList) + val (tparams1, evidenceParams) = desugarTypeBindings(tparams, isPrimaryConstructor) + val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), evidenceParams) /** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */ def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { @@ -293,6 +300,7 @@ object desugar { def isAnyVal(tree: Tree): Boolean = tree match { case Ident(tpnme.AnyVal) => true case Select(qual, tpnme.AnyVal) => isScala(qual) + case TypedSplice(tree) => tree.tpe.isRef(defn.AnyValClass) case _ => false } def isScala(tree: Tree): Boolean = tree match { @@ -749,6 +757,73 @@ object desugar { Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) } + /** extension id for : { } + * -> + * implicit class ($this: ) + * extends { + * import $this._ + * + * } + * + * where + * + * (, ) = desugarTypeBindings() + * = concatenated with in one clause + * = with each occurrence of unqualified `this` substituted by `$this`. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * extension for { } + * -> + * implicit class (private val $this: ) + * extends AnyVal { + * import $this._ + * + * } + * + * where + * + * = where each method definition gets as last parameter section. + */ + def extension(tree: Extension)(implicit ctx: Context): Tree = { + val Extension(name, extended, impl) = tree + val isSimpleExtension = impl.parents.isEmpty + + val firstParams = ValDef(nme.SELF, extended, EmptyTree).withFlags(Private | Local | ParamAccessor) :: Nil + val body1 = substThis.transform(impl.body) + val impl1 = + if (isSimpleExtension) { + val (typeParams, evidenceParams) = + desugarTypeBindings(impl.constr.tparams, forPrimaryConstructor = false) + cpy.Template(impl)( + constr = cpy.DefDef(impl.constr)(tparams = typeParams, vparamss = firstParams :: Nil), + parents = ref(defn.AnyValType) :: Nil, + body = body1.map { + case ddef: DefDef => + def resetFlags(vdef: ValDef) = + vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) + val originalParams = impl.constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) + case other => + other + }) + } + else + cpy.Template(impl)( + constr = cpy.DefDef(impl.constr)(vparamss = firstParams :: impl.constr.vparamss), + body = body1) + val icls = TypeDef(name, impl1).withMods(tree.mods.withAddedMod(Mod.Extension()) | Implicit) + desugr.println(i"desugar $extended --> $icls") + classDef(icls) + } + + private val substThis = new UntypedTreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case This(Ident(tpnme.EMPTY)) => Ident(nme.SELF).withPos(tree.pos) + case _ => super.transform(tree) + } + } + def defTree(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: ValDef => valDef(tree) case tree: TypeDef => if (tree.isClassDef) classDef(tree) else tree @@ -757,6 +832,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) + case tree: Extension => extension(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 7e5982db3b7d..e0585205e55a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -41,7 +41,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } /** extend extended impl */ - case class Extension(name: TypeName, extended: Tree, impl: Template) extends DefTree + case class Extension(name: TypeName, extended: Tree, impl: Template) extends MemberDef case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -143,7 +143,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class EnumCase() extends Mod(Flags.EmptyFlags) - case class InstanceDecl() extends Mod(Flags.EmptyFlags) + case class Extension() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index bd8cb9844c0c..d61764c0a7eb 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,6 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter + val desugr: Printer = new Printer val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter From 20843714471da24735334078ecd0748b02a92add Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 21:07:22 +0100 Subject: [PATCH 31/54] Avoid `extension` as an identifier. It's now a keyword, need to enclose in backticks when used as an identifier. --- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 4 ++-- compiler/src/dotty/tools/dotc/config/Settings.scala | 2 +- compiler/src/dotty/tools/dotc/fromtasty/Debug.scala | 2 +- compiler/src/dotty/tools/io/AbstractFile.scala | 6 +++--- compiler/src/dotty/tools/io/JarArchive.scala | 4 ++-- compiler/src/dotty/tools/io/Path.scala | 12 ++++++------ .../dotc/transform/PatmatExhaustivityTest.scala | 4 ++-- tests/neg/i2494.scala | 2 +- tests/pos/t8306.scala | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index ca21867fd711..5d8fe2a37d70 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -57,7 +57,7 @@ class GenBCode extends Phase { if (myOutput eq null) { val path = Directory(ctx.settings.outputDir.value) myOutput = - if (path.extension == "jar") JarArchive.create(path) + if (path.`extension` == "jar") JarArchive.create(path) else new PlainDirectory(path) } myOutput diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 8ed5398ef5f4..047e23d2e291 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -785,7 +785,7 @@ object desugar { * * = where each method definition gets as last parameter section. */ - def extension(tree: Extension)(implicit ctx: Context): Tree = { + def extensionDef(tree: Extension)(implicit ctx: Context): Tree = { val Extension(name, extended, impl) = tree val isSimpleExtension = impl.parents.isEmpty @@ -832,7 +832,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) - case tree: Extension => extension(tree) + case tree: Extension => extensionDef(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 6343341b29f4..750bec00989a 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -144,7 +144,7 @@ object Settings { Path(arg) match { case _: Directory => update(arg, args) - case p if p.extension == "jar" => + case p if p.`extension` == "jar" => update(arg, args) case _ => fail(s"'$arg' does not exist or is not a directory", args) diff --git a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala index f44ad7d63714..48abbf6de36b 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala @@ -36,7 +36,7 @@ object Debug { val fromTastyOut = Files.createDirectory(tmpOut.resolve("from-tasty")) val ext = "hasTasty" - val classes = Directory(fromSourcesOut).walk.filter(x => x.isFile && x.extension == ext).map { x => + val classes = Directory(fromSourcesOut).walk.filter(x => x.isFile && x.`extension` == ext).map { x => val source = x.toString // transform foo/bar/Baz.hasTasty into foo.bar.Baz source.substring(fromSourcesOut.toString.length + 1, source.length - ext.length - 1).replace('/', '.') diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index d0155ace3711..748144c6164a 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -93,8 +93,8 @@ abstract class AbstractFile extends Iterable[AbstractFile] { def canonicalPath: String = if (jpath == null) path else jpath.normalize.toString /** Checks extension case insensitively. */ - def hasExtension(other: String) = extension == other.toLowerCase - private val extension: String = Path.extension(name) + def hasExtension(other: String) = `extension` == other.toLowerCase + private val `extension`: String = Path.`extension`(name) /** The absolute file, if this is a relative file. */ def absolute: AbstractFile @@ -122,7 +122,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] { } /** Does this abstract file represent something which can contain classfiles? */ - def isClassContainer = isDirectory || (jpath != null && (extension == "jar" || extension == "zip")) + def isClassContainer = isDirectory || (jpath != null && (`extension` == "jar" || `extension` == "zip")) /** Create a file on disk, if one does not exist already. */ def create(): Unit diff --git a/compiler/src/dotty/tools/io/JarArchive.scala b/compiler/src/dotty/tools/io/JarArchive.scala index 0960160d52b7..f600cdd099a6 100644 --- a/compiler/src/dotty/tools/io/JarArchive.scala +++ b/compiler/src/dotty/tools/io/JarArchive.scala @@ -15,14 +15,14 @@ class JarArchive private (root: Directory) extends PlainDirectory(root) { object JarArchive { /** Create a new jar file. Overwrite if file already exists */ def create(path: Path): JarArchive = { - require(path.extension == "jar") + require(path.`extension` == "jar") path.delete() open(path, create = true) } /** Create a jar file. */ def open(path: Path, create: Boolean = false): JarArchive = { - require(path.extension == "jar") + require(path.`extension` == "jar") // creating a new zip file system by using the JAR URL syntax: // https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html diff --git a/compiler/src/dotty/tools/io/Path.scala b/compiler/src/dotty/tools/io/Path.scala index fbd2a4ac92c7..68bca86d54b4 100644 --- a/compiler/src/dotty/tools/io/Path.scala +++ b/compiler/src/dotty/tools/io/Path.scala @@ -35,10 +35,10 @@ import scala.util.Random.alphanumeric object Path { def isExtensionJarOrZip(jpath: JPath): Boolean = isExtensionJarOrZip(jpath.getFileName.toString) def isExtensionJarOrZip(name: String): Boolean = { - val ext = extension(name) + val ext = `extension`(name) ext == "jar" || ext == "zip" } - def extension(name: String): String = { + def `extension`(name: String): String = { var i = name.length - 1 while (i >= 0 && name.charAt(i) != '.') i -= 1 @@ -138,7 +138,7 @@ class Path private[io] (val jpath: JPath) { if (p isSame this) Nil else p :: p.parents } // if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg"), otherwise "" - def extension: String = { + def `extension`: String = { var i = name.length - 1 while (i >= 0 && name.charAt(i) != '.') i -= 1 @@ -148,17 +148,17 @@ class Path private[io] (val jpath: JPath) { } // compares against extensions in a CASE INSENSITIVE way. def hasExtension(ext: String, exts: String*) = { - val lower = extension.toLowerCase + val lower = `extension`.toLowerCase ext.toLowerCase == lower || exts.exists(_.toLowerCase == lower) } // returns the filename without the extension. - def stripExtension: String = name stripSuffix ("." + extension) + def stripExtension: String = name stripSuffix ("." + `extension`) // returns the Path with the extension. def addExtension(ext: String): Path = new Path(jpath.resolveSibling(name + ext)) // changes the existing extension out for a new one, or adds it // if the current path has none. def changeExtension(ext: String): Path = - if (extension == "") addExtension(ext) + if (`extension` == "") addExtension(ext) else new Path(jpath.resolveSibling(stripExtension + "." + ext)) // conditionally execute diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 53c6e0c53238..9da936d4d3d8 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -45,7 +45,7 @@ class PatmatExhaustivityTest { val reporter = TestReporter.simplifiedReporter(new PrintWriter(stringBuffer)) val files = Directory(path).list.toList - .filter(f => f.extension == "scala" || f.extension == "java" ) + .filter(f => f.`extension` == "scala" || f.`extension` == "java" ) .map(_.jpath.toString) try { @@ -69,7 +69,7 @@ class PatmatExhaustivityTest { @Test def patmatExhaustivity: Unit = { val res = Directory(testsDir).list.toList - .filter(f => f.extension == "scala" || f.isDirectory) + .filter(f => f.`extension` == "scala" || f.isDirectory) .map { f => if (f.isDirectory) compileDir(f.jpath) diff --git a/tests/neg/i2494.scala b/tests/neg/i2494.scala index 21c11242878d..afdd8e92fce5 100644 --- a/tests/neg/i2494.scala +++ b/tests/neg/i2494.scala @@ -1,2 +1,2 @@ -enum +enum // error object // error // error diff --git a/tests/pos/t8306.scala b/tests/pos/t8306.scala index e04b054eb9bb..f6b930bada74 100644 --- a/tests/pos/t8306.scala +++ b/tests/pos/t8306.scala @@ -1,6 +1,6 @@ class Si8306 { def foo: Int = 123 - lazy val extension: Int = + lazy val ext: Int = foo match { case idx if idx != -1 => 15 case _ => 17 From 68ab0bb5a632ae376bb1c0dba34003199d9f8521 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Mar 2018 11:03:07 +0100 Subject: [PATCH 32/54] Pretty printing, fixes, and tests --- .../src/dotty/tools/dotc/ast/Desugar.scala | 20 ++- compiler/src/dotty/tools/dotc/ast/untpd.scala | 21 ++- .../dotty/tools/dotc/config/Printers.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 8 +- .../tools/dotc/printing/RefinedPrinter.scala | 22 ++- .../src/dotty/tools/dotc/typer/Namer.scala | 3 +- .../transform/PatmatExhaustivityTest.scala | 2 +- .../reference/extend/extension-methods.md | 2 +- tests/neg/extensions.scala | 45 +++++ tests/pending/pos/blueSkyExtensions1.scala | 134 ++++++++++++++ tests/pos/opaque-extension.scala | 35 ++++ tests/run/extensions.check | 16 ++ tests/run/extensions.scala | 166 ++++++++++++++++++ 13 files changed, 454 insertions(+), 22 deletions(-) create mode 100644 tests/neg/extensions.scala create mode 100644 tests/pending/pos/blueSkyExtensions1.scala create mode 100644 tests/pos/opaque-extension.scala create mode 100644 tests/run/extensions.check create mode 100644 tests/run/extensions.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 047e23d2e291..5f579e963203 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -786,23 +786,24 @@ object desugar { * = where each method definition gets as last parameter section. */ def extensionDef(tree: Extension)(implicit ctx: Context): Tree = { - val Extension(name, extended, impl) = tree + val Extension(name, constr, extended, impl) = tree val isSimpleExtension = impl.parents.isEmpty val firstParams = ValDef(nme.SELF, extended, EmptyTree).withFlags(Private | Local | ParamAccessor) :: Nil - val body1 = substThis.transform(impl.body) + val importSelf = Import(Ident(nme.SELF), Ident(nme.WILDCARD) :: Nil) + val body1 = importSelf :: substThis.transform(impl.body) val impl1 = if (isSimpleExtension) { val (typeParams, evidenceParams) = - desugarTypeBindings(impl.constr.tparams, forPrimaryConstructor = false) + desugarTypeBindings(constr.tparams, forPrimaryConstructor = false) cpy.Template(impl)( - constr = cpy.DefDef(impl.constr)(tparams = typeParams, vparamss = firstParams :: Nil), + constr = cpy.DefDef(constr)(tparams = typeParams, vparamss = firstParams :: Nil), parents = ref(defn.AnyValType) :: Nil, body = body1.map { case ddef: DefDef => def resetFlags(vdef: ValDef) = vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) - val originalParams = impl.constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + val originalParams = constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) case other => other @@ -810,10 +811,13 @@ object desugar { } else cpy.Template(impl)( - constr = cpy.DefDef(impl.constr)(vparamss = firstParams :: impl.constr.vparamss), + constr = cpy.DefDef(constr)(vparamss = firstParams :: constr.vparamss), body = body1) - val icls = TypeDef(name, impl1).withMods(tree.mods.withAddedMod(Mod.Extension()) | Implicit) - desugr.println(i"desugar $extended --> $icls") + val mods1 = + if (isSimpleExtension) tree.mods + else tree.mods.withAddedMod(Mod.InstanceDcl()) + val icls = TypeDef(name, impl1).withMods(mods1 | Implicit) + desugr.println(i"desugar $tree --> $icls") classDef(icls) } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e0585205e55a..afd9cf35d89a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,8 +40,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } - /** extend extended impl */ - case class Extension(name: TypeName, extended: Tree, impl: Template) extends MemberDef + /** extension name tparams vparamss for tpt impl + * + * where `tparams` and `vparamss` are part of `constr`. + */ + case class Extension(name: TypeName, constr: DefDef, tpt: Tree, impl: Template) + extends MemberDef{ + type ThisTree[-T >: Untyped] <: Trees.NameTree[T] with Trees.MemberDef[T] with Extension + def withName(name: Name)(implicit ctx: Context) = cpy.Extension(this)(name.toTypeName, constr, tpt, impl) + } case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -143,7 +150,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class EnumCase() extends Mod(Flags.EmptyFlags) - case class Extension() extends Mod(Flags.EmptyFlags) + case class InstanceDcl() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions @@ -416,6 +423,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: ModuleDef if (name eq tree.name) && (impl eq tree.impl) => tree case _ => finalize(tree, untpd.ModuleDef(name, impl)) } + def Extension(tree: Tree)(name: TypeName, constr: DefDef, tpt: Tree, impl: Template) = tree match { + case tree: Extension if (name eq tree.name) && (constr eq tree.constr) && (tpt eq tree.tpt) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Extension(name, constr, tpt, impl)) + } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree @@ -499,6 +510,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) + case Extension(name, constr, tpt, impl) => + cpy.Extension(tree)(name, transformSub(constr), transform(tpt), transformSub(impl)) case ParsedTry(expr, handler, finalizer) => cpy.ParsedTry(tree)(transform(expr), transform(handler), transform(finalizer)) case SymbolLit(str) => @@ -548,6 +561,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = tree match { case ModuleDef(name, impl) => this(x, impl) + case Extension(name, constr, tpt, impl) => + this(this(this(x, constr), tpt), impl) case ParsedTry(expr, handler, finalizer) => this(this(this(x, expr), handler), finalizer) case SymbolLit(str) => diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index d61764c0a7eb..db540ebf85b5 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,7 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter - val desugr: Printer = new Printer + val desugr: Printer = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index b6e3f4fd4aa9..99fd6119a28a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2291,16 +2291,16 @@ object Parsers { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Class) val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) - val constr = makeConstructor(Nil, vparamss) + val constr = makeConstructor(tparams, vparamss) accept(FOR) val extended = annotType() val templ = if (in.token == COLON) { in.nextToken() - template(constr, bodyRequired = true)._1 + template(emptyConstructor, bodyRequired = true)._1 } else { - val templ = templateClauseOpt(constr, bodyRequired = true) + val templ = templateClauseOpt(emptyConstructor, bodyRequired = true) def checkDef(tree: Tree) = tree match { case _: DefDef | EmptyValDef => // ok case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) @@ -2309,7 +2309,7 @@ object Parsers { templ.body.foreach(checkDef) templ } - Extension(name, extended, templ) + Extension(name, constr, extended, templ) } /* -------- TEMPLATES ------------------------------------------- */ diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ee9a1df5921c..194fb0ee03ed 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -448,6 +448,17 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { modText(tree.mods, keywordStr("object"), isType = false) ~~ nameIdText(tree) ~ toTextTemplate(impl) } + case tree @ Extension(name, constr, tpt, impl) => + withEnclosingDef(tree) { + modText(tree.mods, keywordStr("extension")) ~~ + nameIdText(tree) ~ + { withEnclosingDef(constr) { + addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1)) + } + } ~ + " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ + toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) + } case SymbolLit(str) => "'" + str case InterpolatedString(id, segments) => @@ -629,7 +640,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { - val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl + val constr @ DefDef(_, tparams, vparamss, _, _) = impl.constr val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) } val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil val prefix: Text = @@ -640,6 +651,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } } + prefix ~ toTextTemplateBody(impl, keywordText(" extends") `provided` !ofNew, primaryConstrs) + } + + protected def toTextTemplateBody(impl: Template, leading: Text, leadingStats: List[Tree[_]] = Nil): Text = { + val Template(_, parents, self, _) = impl val parentsText = Text(parents map constrText, keywordStr(" with ")) val selfText = { val selfName = if (self.name == nme.WILDCARD) keywordStr("this") else self.name.toString @@ -657,9 +673,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { params ::: rest } else impl.body - val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}" + val bodyText = "{" ~~ selfText ~~ toTextGlobal(leadingStats ::: body, "\n") ~ "}" - prefix ~ (keywordText(" extends") provided !ofNew) ~~ parentsText ~~ bodyText + leading ~~ parentsText ~~ bodyText } protected def templateText(tree: TypeDef, impl: Template): Text = { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 089b359ada03..c68a084cfeb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -943,7 +943,8 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.pos, - traitReq = parent ne parents.head, stablePrefixReq = true) + traitReq = (parent `ne` parents.head) || original.mods.hasMod[Mod.InstanceDcl], + stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { case Select(qual: Super, _) if ctx.scala2Mode => diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 9da936d4d3d8..a961860f4c08 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -78,7 +78,7 @@ class PatmatExhaustivityTest { } val failed = res.filter { case (_, expected, actual) => expected != actual } - val ignored = Directory(testsDir).list.toList.filter(_.extension == "ignore") + val ignored = Directory(testsDir).list.toList.filter(_.`extension` == "ignore") failed.foreach { case (file, expected, actual) => println(s"\n----------------- incorrect output for $file --------------\n" + diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 1790f89b6e45..c4947391e513 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -122,7 +122,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) - extenson Ensuring[T] for T { + extension Ensuring[T] for T { def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(this) assert(condition) diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala new file mode 100644 index 000000000000..bfb720a6ac28 --- /dev/null +++ b/tests/neg/extensions.scala @@ -0,0 +1,45 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extension CircleOps for Circle { + def circumference = this.radius * math.Pi * 2 + private val p = math.Pi // error: `def` expected + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + abstract class HasAreaClass extends HasArea + + extension Ops2 : HasArea {} // error: `def` expected + extension Ops for Circle extends HasArea {} // error: `def` expected + + extension Circle2 : HasArea { // error: `for` expected + def area = this.radius * this.radius * math.Pi + } + + extension Ops3 for Circle : HasAreaClass { // error: class HasAreaClass is not a trait + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + extension ListOps[T] for List[T] { + type I = Int // error: `def` expected + def second = this.tail.head + } + +// Specific trait implementations + + extension ListOps2 for List[Int] { self => // error: `def` expected + import java.lang._ // error: `def` expected + def maxx = (0 /: this)(_ `max` _) + } +} diff --git a/tests/pending/pos/blueSkyExtensions1.scala b/tests/pending/pos/blueSkyExtensions1.scala new file mode 100644 index 000000000000..56ec9f6e5ac7 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions1.scala @@ -0,0 +1,134 @@ +/** A blue sky sketch how one might evolve extensions to do type classes + * without the current gap between functional and OO patterns. + * Largely inspired by the way Rust does it. + * + * The main additions (to be worked out in detail) is a self type `This` and + * a mechanism that a trait can abstract over companions of classes that implement it. + * Companion types and methods are declared using `static` for now, just to + * pick some familiar notation. + * + * Ideas about `This` (still vague at the moment) + * + * - Treat it as an additional abstract type in a trait, prefixed by the name of the trait + * - An implementing (non-trait) class binds the `This` types of all the traits it implements + * to itself. + * - Later subclasses do not re-bind `This` types already bound by their superclasses. + * (so in that sense, `This`-binding is like trait parameterization, the first implementing + * classes determines the self type and the parameters of a trait) + * - Paramerized classes have parameterized `This` types (e.g. Functor below). + */ +import Predef.{any2stringadd => _, _} +object blueSkyExtensions { + +// Semigroup and Monoid + + trait SemiGroup { + def + (that: This): This + } + + trait Monoid extends SemiGroup { + static def unit: This + } + + extension IntMonoid for Int : Monoid { + static def unit = 0 + def + (that: Int) = this + that + } + + extension StringMonoid for String : Monoid { + static def unit = "" + def + (that: Int) = this ++ that + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + static def pure[A]: This[A] + def map[B](f: A => B): This[B] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.statics[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extension ListMonad[T] for List[T] : Monad[T] { + static def pure[A] = Nil + + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + + extension MonadFlatten[T[X]: Monad[X]] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + static def empty: This[A] + static def apply(xs: A*): This[A] + + def filter(p: A => Boolean): This[A] + } + + trait Iterable[A] extends MonoIterable[A] { + def map[B](f: A => B): This[B] + def flatMap[B](f: A => This[B]): This[B] + } + + extension StringMonoIterable for String : MonoIterable[Char] { + static type This[A] = String + static def empty = "" + static def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + + extension StringIterable for String : Iterable[Char] { + static type This[A] = IndexedSeq[A] + + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extension ListIterable[T] for List[T] : Iterable[T] { + static type This[A] = List[A] + static def empty = Nil + static def apply(xs: A*) = (xs /: Nil)(_ :: _) + + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } +} diff --git a/tests/pos/opaque-extension.scala b/tests/pos/opaque-extension.scala new file mode 100644 index 000000000000..69230356b0dc --- /dev/null +++ b/tests/pos/opaque-extension.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + extension LogarithmOps for Logarithm { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(this) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(this) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(this + that) + } + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l * l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = l3.toDouble + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/run/extensions.check b/tests/run/extensions.check new file mode 100644 index 000000000000..748a6ca11e55 --- /dev/null +++ b/tests/run/extensions.check @@ -0,0 +1,16 @@ +12.566370614359172 +(1,a) +true +false +true +false +true +false +true +false +2 +List(1, 2, 3) +3 +3 +false +true diff --git a/tests/run/extensions.scala b/tests/run/extensions.scala new file mode 100644 index 000000000000..1ed7ff89bda5 --- /dev/null +++ b/tests/run/extensions.scala @@ -0,0 +1,166 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extension CircleOps for Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + extension CircleHasArea for Circle : HasArea { + def area = radius * this.radius * math.Pi + } + +// Generic extendations + + extension ListOps[T] for List[T] { + def second = tail.head + } + +// Specific extendations + + extension IntListOps for List[Int] { + def maxx = (0 /: this)(_ `max` _) + } + + extension IntArrayOps for Array[Int] { + def maxx = (0 /: this)(_ `max` _) + } + +// Conditional extension methods + + case class Rectangle[T](x: T, y: T, width: T, height: T) + + trait Eql[T] { + def eql (x: T, y: T): Boolean + } + + extension RectangleOps[T: Eql] for Rectangle[T] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + + extension RectangleOps2[T](implicit ev: Eql[T]) for Rectangle[T] { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(width, height) + } + +// Simple generic extensions + + extension Pairer[T] for T { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic extensions + + trait HasEql[T] { + def === (that: T): Boolean + } + + extension HasEqlDeco[T : Eql] for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + extension InfixEql[T](implicit ev: Eql[T]) for T { + def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + extension RectangleHasEql[T : Eql] for Rectangle[T] : HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } +} + +object extensions2 { + import extensions.{Eql, HasEqlDeco} + // Nested generic arguments + + extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = (this :\ (Nil: List[T]))(_ ++ _) + } + + extension EqlDeco[T : Eql] for (T, T) { + def isSame = this._1 === this._2 + def isSame2 = HasEqlDeco(this._1) === this._2 + } + +} + +object docs { + + extension StringOps for Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = map(_.length).max + filter(_.length == maxLength) + } + } + + extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = foldLeft[List[T]](Nil)(_ ++ _) + } + + extension OrdSeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 + } + + object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + extension Ensuring[T] for T { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } + } + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} + +import extensions._ +import extensions2._ +object Test extends App { + val c = Circle(0, 1, 2) + println(c.area) + + implicit object IntHasEql extends Eql[Int] { + def eql (x: Int, y: Int): Boolean = x == y + } + + println(1 ~ "a") + + val r1 = Rectangle(0, 0, 2, 2) + val r2 = Rectangle(0, 0, 2, 3) + println(r1.isSquare) + println(r2.isSquare) + println(r2.isNotSquare) + println(r1.isNotSquare) + println(r1 === r1) + println(r1 === r2) + println(1 ==== 1) + println(1 ==== 2) + println(List(1, 2, 3).second) + println(List(List(1), List(2, 3)).flattened) + println(List(List(1), List(2, 3)).flattened.maxx) + println(Array(1, 2, 3).maxx) + println((2, 3).isSame) + println(EqlDeco((3, 3)).isSame) +} From e318da75ba8b3adb501bd7d564ff95b2ebd59b2a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 18:15:21 +0200 Subject: [PATCH 33/54] Adapt RefinedPrinter to new scheme for modText --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 194fb0ee03ed..83532f865477 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -450,7 +450,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case tree @ Extension(name, constr, tpt, impl) => withEnclosingDef(tree) { - modText(tree.mods, keywordStr("extension")) ~~ + modText(tree.mods, keywordStr("extension"), isType = false) ~~ nameIdText(tree) ~ { withEnclosingDef(constr) { addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1)) From 9c773c6fcd16d23f10a15a44590c2f0ad467ce57 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Mar 2018 11:03:07 +0100 Subject: [PATCH 34/54] Pretty printing, fixes, and tests --- .../dotty/tools/dotc/printing/RefinedPrinter.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 83532f865477..5515149845e2 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -459,6 +459,17 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) } + case tree @ Extension(name, constr, tpt, impl) => + withEnclosingDef(tree) { + modText(tree.mods, keywordStr("extension")) ~~ + nameIdText(tree) ~ + { withEnclosingDef(constr) { + addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1)) + } + } ~ + " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ + toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) + } case SymbolLit(str) => "'" + str case InterpolatedString(id, segments) => From a7b471930e7255a81a83002cae133283b732cdd8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Mar 2018 18:23:40 +0100 Subject: [PATCH 35/54] [Proposal] Common Declarations This is a proposal to add `common` declarations to the language. These declarations are an important stepping stone to full Rust-like type classes. Given what's described here it would be a relatively small step to do the full typeclass encoding. Without the full encoding, the usefulness of `common` definitions is still there but not as great. So it's dubious whether this would pay its cost as a separate feature. Nevertheless, it's nicer to describe this feature in isolation, since we achieve better conceptual modularity that way. --- docs/docs/reference/common-declarations.md | 256 +++++++++++++++++++++ docs/sidebar.yml | 2 + tests/pos/commons.scala | 68 ++++++ tests/pos/commons1.scala | 69 ++++++ tests/pos/typeclass-encoding2.scala | 255 ++++++++++---------- 5 files changed, 526 insertions(+), 124 deletions(-) create mode 100644 docs/docs/reference/common-declarations.md create mode 100644 tests/pos/commons.scala create mode 100644 tests/pos/commons1.scala diff --git a/docs/docs/reference/common-declarations.md b/docs/docs/reference/common-declarations.md new file mode 100644 index 000000000000..84a499f3b592 --- /dev/null +++ b/docs/docs/reference/common-declarations.md @@ -0,0 +1,256 @@ +--- +layout: doc-page +title: "Common Declarations" +--- + +`common` declarations and definitions are a way to specify members of the companion object of a class. Unlike `static` definitions in Java, `common` declarations can be inherited. + +As an example, consider the following trait `Text` with an implementation class `FlatText`. + +```scala +trait Text { + def length: Int + def apply(idx: Int): Char + def concat(txt: Text): Text + def toStr: String + + common def fromString(str: String): Text + common def fromStrings(strs: String*): Text = + ("" :: strs).map(fromString).reduceLeft(_.concat) +} + +class FlatText(str: String) extends Text { + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: Text): Text = new FlatText(str ++ txt.toStr) + def toStr = str + + common def fromString(str: String) = new FlatText(str) +} +``` + +The `common` definition of `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: + +```scala +val txt1 = FlatText.fromString("hello") +val txt2 = FlatText.fromStrings("hello", ", world") +``` + +`common` definitions are only members of the companion objectcs of classes, not traits. So the following would give a "member not found" error. + +```scala +val erroneous = Text.fromStrings("hello", ", world") // error: not found +``` + +## The `Instance` type + +In the previous example, the argument and result type of `concat` is just `Text`. So every implementation of `Text` has to be prepared to concatenate all possible implementatons of `Text`. Furthermore, we hide the concrete implementation type in the result type of `concat` and of the construction methods `fromString` and `fromStrings`. Sometimes we want a different design that specifyfies the actual implementation type instead of the base trait `Text`. We can refer to this type using the predefined type `Instance`: + +```scala +trait Text { + def length: Int + def apply(idx: Int): Char + def concat(txt: Instance): Instance + def toStr: String + + common def fromString(str: String): Instance + common def fromStrings(strs: String*): Instance = + ("" :: strs).map(fromString).reduceLeft(_.concat) +} +``` + +In traits that define or inherit `common` definitions, the `Instance` type refers to the (as yet unknown) instance type whose +companion object implements the trait. To see why `Instance` is useful, consider another possible implementation of `Text`, implemented as a tree of strings. The advantage of the new implementation is that `concat` is constant time. Both old and new implementations share the definition of the `common` method `fromStrings`. + +```scala +enum ConcText extends Text { + case Str(s: String) + case Conc(t1: ConcText, t2: ConcText) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: ConcText) = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } + common def fromString(str: String): ConcText = Str(str) +} +``` + +The `concat` method of `ConcText` with type `(txt: ConcText): ConcText` is a valid implementation of the +abstract method in `Text` of type `(txt: Instance): Instance` because `ConcText` is a class implementing `Text` +which means that it fixes `Instance` to be `ConcText`. + +Note: The `Instance` type is a useful abstraction for traits that are always implemented via `extends`. For type-class like traits that are intended to be implemented after the fact with extension clauses, there is another predefined type `This` that is generally more appropriate (more on `This` in the typeclass section). + +## The `common` Reference + +Let's add another method to `Text`: + + trait Text { + ... + def flatten: Instance = fromString(toStr) + } + +Why does this work? The `fromString` method is abstract in `Text` so how to we find the correct implementation in `flatten`? +Comparing with the `toStr` reference, this one is an instance method and therefore is expanded to `this.toStr`. But the same does not work for `fromString` because it is a `common` method, not an instance method. +In fact, the application above is syntactic sugar for + + this.common.fromString(this.toStr) + +The `common` selector is defined in each trait that defines or inherits `common` definitions. It refers at runtime to +the object that implements the `common` definitions. + +## Translation + +The translation of a trait `T` that defines `common` declarations `common D1, ..., common Dn` +and extends traits with common declarations `P1`, ..., Pn` is as follows: +All `common` definitions are put in a trait `T.Common` which is defined in `T`'s companion object: + + object T { + trait Common extends P1.Common with ... with Pn.Common { self => + type Instance <: T { val `common`: self.type } + D1 + ... + Dn + } + } + +The trait inherits all `Common` traits associated with `T`'s parent traits. If no explicit definition of `Instance` is +given, it declares the `Instance` type as shown above. The trait `T` itself is expanded as follows: + + trait T extends ... { + val `common`: `T.Common` + import `common`._ + + ... + } + +Any direct reference to `x.common` in the body of `T` is simply translated to + + x.`common` + +The translation of a class `C` that defines `common` declarations `common D1, ..., common Dn` +and extends traits with common declarations `P1`, ..., Pn` is as follows: +All `common` definitions of the class itself are placed in `C`'s companion object, which also inherits all +`Common` traits of `C`'s parents. If `C` already defines a companion object, the synthesized parents +come after the explicitly declared ones, whereas the common definitions precede all explicitly given statements of the +companion object. The companion object also defines the `Instance` type as + + type Instance = C + +unless an explicit definition of `Instance` is given in the same object. + +### Example: + +As an example, here is the translation of trait `Text` and its two implementations `FlatText` and `ConcText`: + +```scala +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Instance): Instance + def toStr: String + def flatten = `common`.fromString(toStr) +} +object Text { + trait Common { self => + type Instance <: Text { val `common`: self.type } + def fromString(str: String): Instance + def fromStrings(strs: String*): Instance = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val `common`: FlatText.type = FlatText + import `common`._ + + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + type Instance = FlatText + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val `common`: ConcText.type = ConcText + import `common`._ + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: ConcText): ConcText = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + type Instance = ConcText + def fromString(str: String) = Str(str) +} +``` + +### Relationship with Parameterization + +Common definitions do not see the type parameters of their enclosing class or trait. So the following is illegal: + +```scala +trait T[A] { + common def f: T[A] +} +``` + +The implicit `Instance` declaration of a trait or class follows in its parameters the parameters of the +trait or class. For instance: + +```scala +trait Sequence[+T <: AnyRef] { + def map[U <: AnyRef](f: T => U): Instance[U] + + common def empty[T]: Instance[T] +} +``` +The implicitly defined `Instance` declaration would be in this case: + +```scala +object Sequence { + trait Common { self => + type Instance[+T <: AnyRef] <: Sequence[T] { val `common`: self.type } + } +} +``` + +The rules for mixing `Instance` definitions of different kinds depend on the status of #4150. If #4150 is +accepted, we permit `Instance` definitions to co-exist at different kinds. If #4150 is not accepted, we +have to forbid this case, which means that a class must have the same parameter structure as all the traits +with common members that it extends. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index e247849662e0..91993880003f 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -43,6 +43,8 @@ sidebar: url: docs/reference/multiversal-equality.html - title: Trait Parameters url: docs/reference/trait-parameters.html + - title: Common Declarations + url: docs/reference/common-declarations.html - title: Inline url: docs/reference/inline.html - title: Meta Programming diff --git a/tests/pos/commons.scala b/tests/pos/commons.scala new file mode 100644 index 000000000000..62056fec4a54 --- /dev/null +++ b/tests/pos/commons.scala @@ -0,0 +1,68 @@ +/** Simple common definitions, no This type */ +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Text): Text + def toStr: String +} +object Text { + trait Common { self => + type Instance <: Text { val `common`: self.type } + def fromString(str: String): Text + def fromStrings(strs: String*): Text = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val common: FlatText.type = FlatText + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: Text) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + type Instance = FlatText + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val common: ConcText.type = ConcText + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: Text) = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + type Instance = ConcText + def fromString(str: String) = Str(str) +} + +object Test extends App { + val txt1 = FlatText.fromStrings("hel", "lo") + val txt2 = ConcText.fromString("world") + println(txt2.concat(txt1)) + assert(txt1.concat(txt2).toStr == "helloworld") + assert(txt2.concat(txt1).toStr == "worldhello") + assert(txt1.concat(txt2)(5) == 'w') +} \ No newline at end of file diff --git a/tests/pos/commons1.scala b/tests/pos/commons1.scala new file mode 100644 index 000000000000..94efd9177fb0 --- /dev/null +++ b/tests/pos/commons1.scala @@ -0,0 +1,69 @@ +/** Simple common definitions, with This type */ +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Instance): Instance + def toStr: String + def flatten = common.fromString(toStr) +} +object Text { + trait Common { self => + type Instance <: Text { val `common`: self.type } + def fromString(str: String): Instance + def fromStrings(strs: String*): Instance = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val common: FlatText.type = FlatText + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + type Instance = FlatText + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val common: ConcText.type = ConcText + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: ConcText): ConcText = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + type Instance = ConcText + def fromString(str: String) = Str(str) +} + +object Test extends App { + val txt1 = ConcText.fromStrings("hel", "lo") + val txt2 = ConcText.fromString("world") + println(txt2.concat(txt1)) + assert(txt1.concat(txt2).toStr == "helloworld") + assert(txt2.concat(txt1).toStr == "worldhello") + assert(txt1.concat(txt2)(5) == 'w') +} \ No newline at end of file diff --git a/tests/pos/typeclass-encoding2.scala b/tests/pos/typeclass-encoding2.scala index 3269430435de..3ddf1ef824f1 100644 --- a/tests/pos/typeclass-encoding2.scala +++ b/tests/pos/typeclass-encoding2.scala @@ -2,25 +2,21 @@ trait SemiGroup extends TypeClass { def add(that: This): This + def add2(that: This): This = add(that).add(that) } - trait Monoid extends SemiGroup - common { - def unit: This + trait Monoid extends SemiGroup { + common def unit: This } extension IntOps for Int : Monoid { def add(that: Int) = this + that - } - common { - def unit = 0 + common def unit = 0 } extension StringOps for String : Monoid { def add(that: Int) = this ++ that - } - common { - def unit = "" + common def unit = "" } enum Nat extends Monoid { @@ -42,69 +38,87 @@ object runtime { trait TypeClass { - val commons: TypeClassCommon - type This = commons.This + /** The companion object of the implementing type */ + val `common`: TypeClass.Common } - trait TypeClassCommon { self => - type This - type Instance <: TypeClass - def inject(x: This): Instance { val commons: self.type } - } + object TypeClass { + + /** Base trait for companion objects of all implementations of this typeclass */ + trait Common { self => + + /** The implementating type */ + type This + + /** The implemented typeclass */ + type Instance <: TypeClass { val `common`: self.type } + + /** Create instances of typeclass from instances of the implementing type */ + implicit def decorate(x: This): Instance + } - trait TypeClassCompanion { - type Impl[T] <: TypeClassCommon { type This = T } - def impl[T](implicit ev: Impl[T]): Impl[T] = ev + /** Base trait for the companion objects of type classes themselves */ + trait Companion { + + /** The `Common` base trait defining common (static) operations of this typeclass */ + type Common <: TypeClass.Common + + /** Helper type to characterize implementations via type `T` for this typeclass */ + type Impl[T] = Common { type This = T } + + /** The implementation via type `T` for this typeclass, as found by implicit search */ + def impl[T](implicit ev: Impl[T]): Impl[T] = ev + } } - implicit def inject[From](x: From) - (implicit ev: TypeClassCommon { type This = From }): ev.Instance { type This = From } = - ev.inject(x) + implicit def decorateTypeClass[From](x: From) + (implicit ev: TypeClass.Common { type This = From }): ev.Instance = + ev.decorate(x) } import runtime._ object semiGroups { trait SemiGroup extends TypeClass { - val commons: SemiGroupCommon - import commons._ + val `common`: SemiGroup.Common + import `common`._ def add(that: This): This + def add2(that: This): This = add(that).add(that) } - trait SemiGroupCommon extends TypeClassCommon { - type Instance <: SemiGroup - } - object SemiGroup extends TypeClassCompanion { - type Impl[T] = SemiGroupCommon { type This = T } + + object SemiGroup extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type Instance <: SemiGroup { val `common`: self.type } + } } trait Monoid extends SemiGroup { - val commons: MonoidCommon - import commons._ - } - trait MonoidCommon extends SemiGroupCommon { - type Instance <: Monoid - def unit: This + val `common`: Monoid.Common + import `common`._ } - object Monoid extends TypeClassCompanion { - type Impl[T] = MonoidCommon { type This = T } + object Monoid extends TypeClass.Companion { + trait Common extends SemiGroup.Common { self => + type Instance <: Monoid { val `common`: self.type } + def unit: This + } } - implicit object IntOps extends MonoidCommon { + implicit object IntOps extends Monoid.Common { type This = Int - type Instance = Monoid + type Instance = Monoid { val `common`: IntOps.type } def unit: Int = 0 - def inject($this: Int) = new Monoid { - val commons: IntOps.this.type = IntOps.this + def decorate($this: Int) = new Monoid { + val `common`: IntOps.this.type = IntOps.this def add(that: This): This = $this + that } } - implicit object StringOps extends MonoidCommon { + implicit object StringOps extends Monoid.Common { type This = String - type Instance = Monoid + type Instance = Monoid { val `common`: StringOps.type } def unit = "" - def inject($this: String) = new Monoid { - val commons: StringOps.this.type = StringOps.this + def decorate($this: String) = new Monoid { + val `common`: StringOps.this.type = StringOps.this def add(that: This): This = $this.concat(that) } } @@ -118,19 +132,19 @@ object semiGroups { case S(n) => S(n.add(that)) } - val commons: Nat.type = Nat + val `common`: Nat.type = Nat } - object Nat extends MonoidCommon { + object Nat extends Monoid.Common { type This = Nat type Instance = Nat def unit = Nat.Z - def inject($this: Nat) = $this + def decorate($this: Nat) = $this } import Nat.{Z, S} implicit def NatOps: Nat.type = Nat - def sum[T](xs: List[T])(implicit ev: Monoid.Impl[T]) = + def sum[T](xs: List[T])(implicit $ev: Monoid.Impl[T]) = (Monoid.impl[T].unit /: xs)((x, y) => x `add` y) sum(List(1, 2, 3)) @@ -144,17 +158,15 @@ object semiGroups { def compareTo(that: This): Int def < (that: This) = compareTo(that) < 0 def > (that: This) = compareTo(that) > 0 - } - common { - val minimum: This + + common val minimum: This } extension IntOrd for Int : Ord { def compareTo(that: Int) = if (this < that) -1 else if (this > that) +1 else 0 - } - common { - val minimum = Int.MinValue + + common val minimum = Int.MinValue } extension ListOrd[T : Ord] for List[T] : Ord { @@ -166,9 +178,8 @@ object semiGroups { val fst = x.compareTo(y) if (fst != 0) fst else xs.compareTo(ys) } - } - common { - val minimum = Nil + + common val minimum = Nil } def min[T: Ord](x: T, y: T) = if (x < y) x else y @@ -178,39 +189,38 @@ object semiGroups { object ord { trait Ord extends TypeClass { - val commons: OrdCommon - import commons._ + val `common`: Ord.Common + import `common`._ def compareTo(that: This): Int def < (that: This) = compareTo(that) < 0 def > (that: This) = compareTo(that) > 0 } - trait OrdCommon extends TypeClassCommon { - type Instance <: Ord - def minimum: This - } - object Ord extends TypeClassCompanion { - type Impl[T] = OrdCommon { type This = T } + object Ord extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type Instance <: Ord { val `common`: self.type } + def minimum: This + } } - implicit object IntOrd extends OrdCommon { + implicit object IntOrd extends Ord.Common { type This = Int - type Instance = Ord + type Instance = Ord { val `common`: IntOrd.type } val minimum: Int = Int.MinValue - def inject($this: Int) = new Ord { - val commons: IntOrd.this.type = IntOrd.this - import commons._ + def decorate($this: Int) = new Ord { + val `common`: IntOrd.this.type = IntOrd.this + import `common`._ def compareTo(that: This): Int = if (this < that) -1 else if (this > that) +1 else 0 } } - class ListOrd[T](implicit ev: Ord.Impl[T]) extends OrdCommon { self => + class ListOrd[T](implicit $ev: Ord.Impl[T]) extends Ord.Common { self => type This = List[T] - type Instance = Ord + type Instance = Ord { val `common`: self.type } def minimum: List[T] = Nil - def inject($this: List[T]) = new Ord { - val commons: self.type = self - import commons._ + def decorate($this: List[T]) = new Ord { + val `common`: self.type = self + import `common`._ def compareTo(that: List[T]): Int = ($this, that) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -222,13 +232,13 @@ object ord { } } - implicit def listOrd[T](implicit ev: Ord.Impl[T]): ListOrd[T] = + implicit def listOrd[T](implicit $ev: Ord.Impl[T]): ListOrd[T] = new ListOrd[T] - def min[T](x: T, y: T)(implicit ev: Ord.Impl[T]): T = + def min[T](x: T, y: T)(implicit $ev: Ord.Impl[T]): T = if (x < y) x else y - def inf[T](xs: List[T])(implicit ev: Ord.Impl[T]): T = { + def inf[T](xs: List[T])(implicit $ev: Ord.Impl[T]): T = { val smallest = Ord.impl[T].minimum (smallest /: xs)(min) } @@ -242,9 +252,8 @@ object ord { trait Functor[A] extends TypeClass1 { def map[B](f: A => B): This[B] - } - common { - def pure[A](x: A): This[A] + + common def pure[A](x: A): This[A] } // Generically, `pure[A]{.map(f)}^n` @@ -258,12 +267,11 @@ object ord { } extension ListMonad[T] for List[T] : Monad[T] { - static def pure[A] = Nil - def flatMap[B](f: A => List[B]): List[B] = this match { case x :: xs => f(x) ++ xs.flatMap(f) case Nil => Nil } + common def pure[A] = Nil } extension MonadFlatten[T[X]: Monad[X]] for T[T[A]] { @@ -272,75 +280,74 @@ object ord { */ object runtime1 { - trait TypeClass1 { - val commons: TypeClassCommon1 - type This = commons.This - } - - trait TypeClassCommon1 { self => - type This[X] - type Instance[X] <: TypeClass1 - def inject[A](x: This[A]): Instance[A] { val commons: self.type } + trait TypeClass1[X] { + val `common`: TypeClass1.Common } + object TypeClass1 { + trait Common { self => + type This[X] + type Instance[X] <: TypeClass1[X] { val `common`: self.type } + def decorate[A](x: This[A]): Instance[A] + } - trait TypeClassCompanion1 { - type Impl[T[_]] <: TypeClassCommon1 { type This = T } - def impl[T[_]](implicit ev: Impl[T]): Impl[T] = ev + trait Companion { + type Common <: TypeClass1.Common + type Impl[T[_]] = Common { type This = T } + def impl[T[_]](implicit ev: Impl[T]): Impl[T] = ev + } } - implicit def inject1[A, From[_]](x: From[A]) - (implicit ev: TypeClassCommon1 { type This = From }): ev.Instance[A] { type This = From } = - ev.inject(x) + implicit def decorateTypeClass1[A, From[_]](x: From[A]) + (implicit ev: TypeClass1.Common { type This = From }): ev.Instance[A] = + ev.decorate(x) } import runtime1._ object functors { - trait Functor[A] extends TypeClass1 { - val commons: FunctorCommon - import commons._ + trait Functor[A] extends TypeClass1[A] { + val `common`: Functor.Common + import `common`._ def map[B](f: A => B): This[B] } - trait FunctorCommon extends TypeClassCommon1 { - type Instance[X] <: Functor[X] - def pure[A](x: A): This[A] - } - object Functor extends TypeClassCompanion1 { - type Impl[T[_]] = FunctorCommon { type This = T } + object Functor extends TypeClass1.Companion { + trait Common extends TypeClass1.Common { self => + type Instance[X] <: Functor[X] { val `common`: self.type } + def pure[A](x: A): This[A] + } } trait Monad[A] extends Functor[A] { - val commons: MonadCommon - import commons._ + val `common`: Monad.Common + import `common`._ def flatMap[B](f: A => This[B]): This[B] - def map[B](f: A => B) = this.flatMap(f.andThen(commons.pure)) + def map[B](f: A => B) = this.flatMap(f.andThen(`common`.pure)) } - trait MonadCommon extends FunctorCommon { - type Instance[X] <: Monad[X] - } - object Monad extends TypeClassCompanion1 { - type Impl[T[_]] = MonadCommon { type This = T } + object Monad extends TypeClass1.Companion { + trait Common extends Functor.Common { self => + type Instance[X] <: Monad[X] { val `common`: self.type } + } } - def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit ev: Functor.Impl[F]): F[A] = + def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.Impl[F]): F[A] = if (n == 0) Functor.impl[F].pure(x) else develop(n - 1, x, f).map(f) - implicit object ListMonad extends MonadCommon { + implicit object ListMonad extends Monad.Common { type This = List - type Instance = Monad + type Instance[X] = Monad[X] { val `common`: ListMonad.type } def pure[A](x: A) = x :: Nil - def inject[A]($this: List[A]) = new Monad[A] { - val commons: ListMonad.this.type = ListMonad - import commons._ + def decorate[A]($this: List[A]) = new Monad[A] { + val `common`: ListMonad.this.type = ListMonad + import `common`._ def flatMap[B](f: A => List[B]): List[B] = $this.flatMap(f) } } object MonadFlatten { - def flattened[T[_], A]($this: T[T[A]])(implicit ev: Monad.Impl[T]): T[A] = + def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.Impl[T]): T[A] = $this.flatMap(identity ) } MonadFlatten.flattened(List(List(1, 2, 3), List(4, 5))) -} \ No newline at end of file +} From 9a220cf5a01a0ff89242925ac458af9e083e61a4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Mar 2018 18:31:48 +0100 Subject: [PATCH 36/54] Fix typos --- docs/docs/reference/common-declarations.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/reference/common-declarations.md b/docs/docs/reference/common-declarations.md index 84a499f3b592..6e99b6f73034 100644 --- a/docs/docs/reference/common-declarations.md +++ b/docs/docs/reference/common-declarations.md @@ -29,7 +29,7 @@ class FlatText(str: String) extends Text { } ``` -The `common` definition of `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: +The `common` method `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: ```scala val txt1 = FlatText.fromString("hello") @@ -44,7 +44,7 @@ val erroneous = Text.fromStrings("hello", ", world") // error: not found ## The `Instance` type -In the previous example, the argument and result type of `concat` is just `Text`. So every implementation of `Text` has to be prepared to concatenate all possible implementatons of `Text`. Furthermore, we hide the concrete implementation type in the result type of `concat` and of the construction methods `fromString` and `fromStrings`. Sometimes we want a different design that specifyfies the actual implementation type instead of the base trait `Text`. We can refer to this type using the predefined type `Instance`: +In the previous example, the argument and result type of `concat` is just `Text`. So every implementation of `Text` has to be prepared to concatenate all possible implementatons of `Text`. Furthermore, we hide the concrete implementation type in the result type of `concat` and of the construction methods `fromString` and `fromStrings`. Sometimes we want a different design that specifies the actual implementation type instead of the base trait `Text`. We can refer to this type using the predefined type `Instance`: ```scala trait Text { @@ -114,8 +114,8 @@ the object that implements the `common` definitions. ## Translation The translation of a trait `T` that defines `common` declarations `common D1, ..., common Dn` -and extends traits with common declarations `P1`, ..., Pn` is as follows: -All `common` definitions are put in a trait `T.Common` which is defined in `T`'s companion object: +and extends traits with common declarations `P1, ..., Pn` is as follows: +All `common` declarations are put in a trait `T.Common` which is defined in `T`'s companion object: object T { trait Common extends P1.Common with ... with Pn.Common { self => @@ -130,7 +130,7 @@ The trait inherits all `Common` traits associated with `T`'s parent traits. If n given, it declares the `Instance` type as shown above. The trait `T` itself is expanded as follows: trait T extends ... { - val `common`: `T.Common` + val `common`: T.Common import `common`._ ... @@ -141,7 +141,7 @@ Any direct reference to `x.common` in the body of `T` is simply translated to x.`common` The translation of a class `C` that defines `common` declarations `common D1, ..., common Dn` -and extends traits with common declarations `P1`, ..., Pn` is as follows: +and extends traits with common declarations `P1, ..., Pn` is as follows: All `common` definitions of the class itself are placed in `C`'s companion object, which also inherits all `Common` traits of `C`'s parents. If `C` already defines a companion object, the synthesized parents come after the explicitly declared ones, whereas the common definitions precede all explicitly given statements of the From 61b179b1eb02388532b078300b0d01e71d25b78f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Mar 2018 15:20:17 +0100 Subject: [PATCH 37/54] Small changes to test --- tests/pos/commons.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/pos/commons.scala b/tests/pos/commons.scala index 62056fec4a54..0c0cab283d03 100644 --- a/tests/pos/commons.scala +++ b/tests/pos/commons.scala @@ -7,10 +7,10 @@ trait Text { def apply(idx: Int): Char def concat(txt: Text): Text def toStr: String + def flatten = `common`.fromString(toStr) } object Text { trait Common { self => - type Instance <: Text { val `common`: self.type } def fromString(str: String): Text def fromStrings(strs: String*): Text = ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) @@ -19,13 +19,14 @@ object Text { class FlatText(str: String) extends Text { val common: FlatText.type = FlatText + import `common`._ + def length = str.length def apply(n: Int) = str.charAt(n) def concat(txt: Text) = new FlatText(str ++ txt.toStr) def toStr = str } object FlatText extends Text.Common { - type Instance = FlatText def fromString(str: String) = new FlatText(str) } @@ -45,7 +46,7 @@ enum ConcText extends Text { case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) } - def concat(txt: Text) = Conc(this, txt) + def concat(txt: Text): Text = Conc(this, txt) def toStr: String = this match { case Str(s) => s @@ -54,7 +55,6 @@ enum ConcText extends Text { } object ConcText extends Text.Common { - type Instance = ConcText def fromString(str: String) = Str(str) } From 5ce44c6dd596b54c576a332423b1fbc45e63271f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 25 Mar 2018 16:58:33 +0200 Subject: [PATCH 38/54] Uniform extensions Add cases to encoding that show how all possible combinations of - trait / trait with common / extension trait - extending class / extension - monomorphic / generic implementation work out. Make it possible that we can abstract over any of these using context bounds. --- tests/pos/typeclass-encoding3.scala | 752 ++++++++++++++++++++++++++++ 1 file changed, 752 insertions(+) create mode 100644 tests/pos/typeclass-encoding3.scala diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala new file mode 100644 index 000000000000..c6edc4b085a0 --- /dev/null +++ b/tests/pos/typeclass-encoding3.scala @@ -0,0 +1,752 @@ +/* + [T : M] = [T] ... (implicit ev: Injectable[T, M]) if M is a normal trait + = [T] ... (implicit ev: M.Impl[T]) if M is an extension trait + + M.Impl[T] = Common { type This = T } + M.Impl[T[_]] = Common { type This = T } + ... + + Extension traits of different kinds cannot be mixed. + Extension traits can extend normal traits. + Normal traits cannot extend extension traits. +*/ +object runtime { + + trait TypeClass { + /** The companion object of the implementing type */ + val `common`: TypeClass.Common + } + + trait Injector { + type This + type Instance + implicit def inject(x: This): Instance + } + + type Injectable[T, +U] = Injector { type This = T; type Instance <: U } + + implicit def selfInject[U, T <: U]: Injectable[T, U] = + new Injector{ + type This = T + type Instance = U + implicit def inject(x: This): Instance = x + } + + object TypeClass { + + /** Base trait for companion objects of all implementations of this typeclass */ + trait Common extends Injector { self => + + /** The implementating type */ + type This + + /** The implemented typeclass */ + type Instance <: TypeClass { val `common`: self.type } + + /** Create instances of typeclass from instances of the implementing type */ + implicit def inject(x: This): Instance + } + + /** Base trait for the companion objects of type classes themselves */ + trait Companion { + + /** The `Common` base trait defining common (static) operations of this typeclass */ + type Common <: TypeClass.Common + + /** Helper type to characterize implementations via type `T` for this typeclass */ + type Impl[T] = Common { type This = T } + + /** The implementation via type `T` for this typeclass, as found by implicit search */ + def impl[T](implicit ev: Impl[T]): Impl[T] = ev + } + } + + implicit def injectTypeClass[From, U](x: From) + (implicit ev: Injector { type This = From }): ev.Instance = + ev.inject(x) +} + +/** 0. All combinations of + + - trait / trait with common / extension trait + - extending class / extension + - monomorphic / generic implementation + + trait HasLength { + def length: Int + } + + trait HasBoundedLength extends HasLength { + common def limit: Int + } + + extension trait HasBoundedLengthX extends HasBoundedLength { + common def longest: This + } + + class C1(xs: Array[Int]) extends HasLength { + def length = xs.length + } + + class CG1[T](xs: Array[T]) extends HasLength { + def length = xs.length + } + + class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength { + common def limit = 100 + } + + class CG2[T](xs: Array[Int]) extends CG1[T](xs) with HasBoundedLength { + common def limit = 100 + } + + final class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { + common def longest = new C3(new Array[Int](limit)) + } + + final class CG3[T](xs: Array[T])(implicit tag: ClassTag[T]) extends CG2[T](xs) with HasBoundedLengthX { + common def longest = new CG3(new Array[T](limit)) + } + + class D1(val xs: Array[Int]) + class DG1[T](val xs: Array[T]) + + class D2(val xs: Array[Int]) + class DG2[T](val xs: Array[T]) + + class D3(val xs: Array[Int]) + class DG3[T](val xs: Array[T]) + + extension DHasLength for D1 : HasLength { + def length = xs.length + } + + extension DGHasLength[T] for DG1[T] : HasLength { + def length = xs.length + } + + extension DHasBoundedLength for D2 : HasBoundedLength { + def length = xs.length + common def limit = 100 + } + + extension DGHasBoundedLength[T] for DG2[T] : HasBoundedLength { + def length = xs.length + common def limit = 100 + } + + extension DHasBoundedLengthX for D3 : HasBoundedLengthX { + def length = xs.length + common def limit = 100 + common def longest = new D3(new Array[Int](limit)) + } + + extension DGHasBoundedLengthX[T](implicit tag: ClassTag[T]) for DG3[T] : HasBoundedLengthX { + def length = xs.length + common def limit = 100 + common def longest = new DG3(new Array[T](limit)) + } + + def length[T : HasLength](x: T) = x.length + + def lengthOK[T : HasBoundedLength](x: T) = + x.length < x.common.limit + + def lengthOKX[T : HasBoundedLengthX](x: T) = + x.length < HasBoundedLengthX.impl[T].limit + + def longestLengthOK[T : HasBoundedLengthX](implicit tag: ClassTag[T]) = { + val impl = HasBoundedLengthX.impl[T] + impl.longest.length < impl.limit + } + + val xs = Array(1, 2, 3) + val c1 = new C1(xs) + val cg1 = new CG1(xs) + val c2 = new C2(xs) + val cg2 = new CG2(xs) + val c3 = new C3(xs) + val cg3 = new CG3(xs) + + val d1 = new D1(xs) + val dg1 = new DG1(xs) + val d2 = new D2(xs) + val dg2 = new DG2(xs) + val d3 = new D3(xs) + val dg3 = new DG3(xs) + + length(c1) + length(cg1) + length(c2) + length(cg2) + length(c3) + length(cg3) + + length(d1) + length(dg1) + length(d2) + length(dg2) + length(d3) + length(dg3) + + lengthOK(c2) + lengthOK(cg2) + lengthOK(c3) + lengthOK(cg3) + + lengthOK(d2) + lengthOK(dg2) + lengthOK(d3) + lengthOK(dg3) + + lengthOKX(c3) + lengthOKX(cg3) + + lengthOKX(d3) + lengthOKX(dg3) + + longestLengthOK(c3) + longestLengthOK(cg3) + longestLengthOK(d3) + longestLengthOK(cg3) +*/ +object hasLength { + import runtime._ + import reflect.ClassTag + + trait HasLength { + def length: Int + } + + trait HasBoundedLength extends HasLength { + val `common`: HasBoundedLength.Common + import `common`._ + } + + object HasBoundedLength { + trait Common { + def limit: Int + } + } + + trait HasBoundedLengthX extends HasBoundedLength with TypeClass { + val `common`: HasBoundedLengthX.Common + import `common`._ + } + + object HasBoundedLengthX extends TypeClass.Companion { + trait Common extends HasBoundedLength.Common with TypeClass.Common { self => + type Instance <: HasBoundedLengthX { val `common`: self.type } + def limit: Int + def longest: This + } + } + + class C1(xs: Array[Int]) extends HasLength { + def length = xs.length + } + + class CG1[T](xs: Array[T]) extends HasLength { + def length = xs.length + } + + class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength { + val `common`: C2Common = C2 + import `common`._ + } + class C2Common extends HasBoundedLength.Common { + def limit = 100 + } + object C2 extends C2Common + + class CG2[T](xs: Array[T]) extends CG1(xs) with HasBoundedLength { + val `common`: CG2Common[T] = CG2[T] + import `common`._ + } + class CG2Common[T] extends HasBoundedLength.Common { + def limit = 100 + } + object CG2 { + def apply[T] = new CG2Common[T] + } + + class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { + override val `common`: C3Common = C3 + import `common`._ + } + class C3Common extends C2Common with HasBoundedLengthX.Common { self => + type This = C3 + type Instance = C3 { val `common`: self.type } + def inject(x: This): Instance = x.asInstanceOf + + def longest = new C3(new Array[Int](limit)) + } + implicit object C3 extends C3Common + + class CG3[T](xs: Array[T])(implicit tag: ClassTag[T]) extends CG2(xs) with HasBoundedLengthX { + override val `common`: CG3Common[T] = CG3[T] + import `common`._ + } + class CG3Common[T](implicit tag: ClassTag[T]) extends CG2Common[T] with HasBoundedLengthX.Common { self => + type This = CG3[T] + type Instance = CG3[T] { val `common`: self.type } + def inject(x: This): Instance = x.asInstanceOf + + def longest = new CG3(new Array[T](limit)) + } + object CG3 { + def apply[T](implicit tag: ClassTag[T]) = new CG3Common[T] + } + implicit def CG3Common[T](implicit tag: ClassTag[T]): CG3Common[T] = CG3[T] + + class D1(val xs: Array[Int]) + class DG1[T](val xs: Array[T]) + + class D2(val xs: Array[Int]) + class DG2[T](val xs: Array[T]) + + class D3(val xs: Array[Int]) + class DG3[T](val xs: Array[T]) + + implicit object DHasLength extends Injector { + type This = D1 + type Instance = HasLength + def inject(x: D1) = new HasLength { + def length = xs.length + } + } + + class DGHasLength[T] extends Injector { + type This = DG1[T] + type Instance = HasLength + def inject(x: DG1[T]) = new HasLength { + def length = xs.length + } + } + implicit def DGHasLength[T]: DGHasLength[T] = new DGHasLength + + implicit object DHasBoundedLength extends Injector with HasBoundedLength.Common { self => + type This = D2 + type Instance = HasBoundedLength + def inject(x: D2) = new HasBoundedLength { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + } + + class DGHasBoundedLength[T] extends Injector with HasBoundedLength.Common { self => + type This = DG2[T] + type Instance = HasBoundedLength + def inject(x: DG2[T]) = new HasBoundedLength { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + } + implicit def DGHasBoundedLength[T]: DGHasBoundedLength[T] = new DGHasBoundedLength + + implicit object DHasBoundedLengthX extends HasBoundedLengthX.Common { self => + type This = D3 + type Instance = HasBoundedLengthX { val `common`: self.type } + def inject(x: D3) = new HasBoundedLengthX { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + def longest = new D3(new Array[Int](limit)) + } + + class DGHasBoundedLengthX[T](implicit tag: ClassTag[T]) extends HasBoundedLengthX.Common { self => + type This = DG3[T] + type Instance = HasBoundedLengthX { val `common`: self.type } + def inject(x: DG3[T]) = new HasBoundedLengthX { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + def longest = new DG3(new Array[T](limit)) + } + implicit def DGHasBoundedLengthX[T](implicit tag: ClassTag[T]): DGHasBoundedLengthX[T] = new DGHasBoundedLengthX + + def length[T](x: T)(implicit ev: Injectable[T, HasLength]) = x.length + + def lengthOK[T](x: T)(implicit ev: Injectable[T, HasBoundedLength]) = + x.length < x.common.limit + + def lengthOKX[T](x: T)(implicit ev: HasBoundedLengthX.Impl[T]) = + x.length < HasBoundedLengthX.impl[T].limit + + def longestLengthOK[T](implicit ev: HasBoundedLengthX.Impl[T], tag: ClassTag[T]) = { + val impl = HasBoundedLengthX.impl[T] + impl.longest.length < impl.limit + } + + val xs = Array(1, 2, 3) + val c1 = new C1(xs) + val cg1 = new CG1(xs) + val c2 = new C2(xs) + val cg2 = new CG2(xs) + val c3 = new C3(xs) + val cg3 = new CG3(xs) + + val d1 = new D1(xs) + val dg1 = new DG1(xs) + val d2 = new D2(xs) + val dg2 = new DG2(xs) + val d3 = new D3(xs) + val dg3 = new DG3(xs) + + length(c1) + length(cg1) + length(c2) + length(cg2) + length(c3)(selfInject) + length(cg3)(selfInject) + + length(d1) + length(dg1) + length(d2) + length(dg2) + length(d3) + length(dg3) + + lengthOK(c2) + lengthOK(cg2) + lengthOK(c3)(selfInject) + lengthOK(cg3)(selfInject) + + lengthOK(d2) + lengthOK(dg2) + lengthOK(d3) + lengthOK(dg3) + + lengthOKX(c3) + lengthOKX(cg3) + + lengthOKX(d3) + lengthOKX(dg3) + + longestLengthOK[C3] + longestLengthOK[CG3[Int]] + longestLengthOK[D3] + longestLengthOK[DG3[Int]] +} +/** 1. Simple type classes with monomorphic implementations and direct extensions. + + extension trait SemiGroup { + def add(that: This): This + def add2(that: This): This = add(that).add(that) + } + + extension trait Monoid extends SemiGroup { + common def unit: This + } + + extension IntOps for Int : Monoid { + def add(that: Int) = this + that + common def unit = 0 + } + + extension StringOps for String : Monoid { + def add(that: Int) = this ++ that + common def unit = "" + } + + enum Nat extends Monoid { + case Z + case S(n: Nat) + + def add(that: Nat): Nat = this match { + case S => that + case S(n) => S(n.add(that)) + } + } + common { + def unit = Z + } + + def sum[T: Monoid](xs: List[T]): T = + (Monod.impl[T].unit /: xs)(_ `add` _) +*/ + +import runtime._ + +object semiGroups { + + trait SemiGroup extends TypeClass { + val `common`: SemiGroup.Common + import `common`._ + def add(that: This): This + def add2(that: This): This = add(that).add(that) + } + + object SemiGroup extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type Instance <: SemiGroup { val `common`: self.type } + } + } + + trait Monoid extends SemiGroup { + val `common`: Monoid.Common + import `common`._ + } + object Monoid extends TypeClass.Companion { + trait Common extends SemiGroup.Common { self => + type Instance <: Monoid { val `common`: self.type } + def unit: This + } + } + + implicit object IntOps extends Monoid.Common { + type This = Int + type Instance = Monoid { val `common`: IntOps.type } + def unit: Int = 0 + def inject($this: Int) = new Monoid { + val `common`: IntOps.this.type = IntOps.this + def add(that: This): This = $this + that + } + } + + implicit object StringOps extends Monoid.Common { + type This = String + type Instance = Monoid { val `common`: StringOps.type } + def unit = "" + def inject($this: String) = new Monoid { + val `common`: StringOps.this.type = StringOps.this + def add(that: This): This = $this.concat(that) + } + } + + enum Nat extends Monoid { + case Z + case S(n: Nat) + + def add(that: Nat): Nat = this match { + case Z => that + case S(n) => S(n.add(that)) + } + + val `common`: Nat.type = Nat + } + object Nat extends Monoid.Common { + type This = Nat + type Instance = Nat + def unit = Nat.Z + def inject($this: Nat) = $this + } + import Nat.{Z, S} + + implicit def NatOps: Nat.type = Nat + + def sum[T](xs: List[T])(implicit $ev: Monoid.Impl[T]) = + (Monoid.impl[T].unit /: xs)((x, y) => x `add` y) + + sum(List(1, 2, 3)) + sum(List("hello ", "world!")) + sum(List(Z, S(Z), S(S(Z)))) +} + +/** 2. Generic implementations of simple type classes. + + extension trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + + common def minimum: This + } + + extension IntOrd for Int : Ord { + def compareTo(that: Int) = + if (this < that) -1 else if (this > that) +1 else 0 + + common def minimum = Int.MinValue + } + + extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + + common def minimum: List[T] = Nil + } + + def min[T: Ord](x: T, y: T) = if (x < y) x else y + + def inf[T: Ord](xs: List[T]): T = (Ord.impl[T].minimum /: xs)(min) +*/ +object ord { + + trait Ord extends TypeClass { + val `common`: Ord.Common + import `common`._ + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + } + object Ord extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type Instance <: Ord { val `common`: self.type } + def minimum: This + } + } + + implicit object IntOrd extends Ord.Common { + type This = Int + type Instance = Ord { val `common`: IntOrd.type } + val minimum: Int = Int.MinValue + def inject($this: Int) = new Ord { + val `common`: IntOrd.this.type = IntOrd.this + import `common`._ + def compareTo(that: This): Int = + if (this < that) -1 else if (this > that) +1 else 0 + } + } + + class ListOrd[T](implicit $ev: Ord.Impl[T]) extends Ord.Common { self => + type This = List[T] + type Instance = Ord { val `common`: self.type } + def minimum: List[T] = Nil + def inject($this: List[T]) = new Ord { + val `common`: self.type = self + import `common`._ + def compareTo(that: List[T]): Int = ($this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + } + + implicit def listOrd[T](implicit $ev: Ord.Impl[T]): ListOrd[T] = + new ListOrd[T] + + def min[T](x: T, y: T)(implicit $ev: Ord.Impl[T]): T = + if (x < y) x else y + + def inf[T](xs: List[T])(implicit $ev: Ord.Impl[T]): T = { + val smallest = Ord.impl[T].minimum + (smallest /: xs)(min) + } + + inf(List[Int]()) + inf(List(List(1, 2), List(1, 2, 3))) + inf(List(List(List(1), List(2)), List(List(1), List(2), List(3)))) +} + +/** 3. Higher-kinded type classes + + extension trait Functor[A] extends TypeClass1 { + def map[B](f: A => B): This[B] + + common def pure[A](x: A): This[A] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[_] : Functor](n: Int, f: A => A): F[A] = + if (n == 0) Functor.impl[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + extension trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extension ListMonad[T] for List[T] : Monad[T] { + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + common def pure[A] = Nil + } + + extension MonadFlatten[T[_]: Monad] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) + } +*/ +object runtime1 { + + trait TypeClass1[X] { + val `common`: TypeClass1.Common + } + object TypeClass1 { + trait Common { self => + type This[X] + type Instance[X] <: TypeClass1[X] { val `common`: self.type } + def inject[A](x: This[A]): Instance[A] + } + + trait Companion { + type Common <: TypeClass1.Common + type Impl[T[_]] = Common { type This = T } + def impl[T[_]](implicit ev: Impl[T]): Impl[T] = ev + } + } + + implicit def decorateTypeClass1[A, From[_]](x: From[A]) + (implicit ev: TypeClass1.Common { type This = From }): ev.Instance[A] = + ev.inject(x) +} +import runtime1._ + +object functors { + + trait Functor[A] extends TypeClass1[A] { + val `common`: Functor.Common + import `common`._ + def map[B](f: A => B): This[B] + } + object Functor extends TypeClass1.Companion { + trait Common extends TypeClass1.Common { self => + type Instance[X] <: Functor[X] { val `common`: self.type } + def pure[A](x: A): This[A] + } + } + + trait Monad[A] extends Functor[A] { + val `common`: Monad.Common + import `common`._ + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(`common`.pure)) + } + object Monad extends TypeClass1.Companion { + trait Common extends Functor.Common { self => + type Instance[X] <: Monad[X] { val `common`: self.type } + } + } + + def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.Impl[F]): F[A] = + if (n == 0) Functor.impl[F].pure(x) + else develop(n - 1, x, f).map(f) + + implicit object ListMonad extends Monad.Common { + type This = List + type Instance[X] = Monad[X] { val `common`: ListMonad.type } + def pure[A](x: A) = x :: Nil + def inject[A]($this: List[A]) = new Monad[A] { + val `common`: ListMonad.this.type = ListMonad + import `common`._ + def flatMap[B](f: A => List[B]): List[B] = $this.flatMap(f) + } + } + + object MonadFlatten { + def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.Impl[T]): T[A] = + $this.flatMap(identity ) + } + + MonadFlatten.flattened(List(List(1, 2, 3), List(4, 5))) +} From c1c74a3ca9db6719da662e120dfd4426a7e7b50b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Mar 2018 13:58:43 +0200 Subject: [PATCH 39/54] Also add cases where extended traits are used as direct parameters --- tests/pos/typeclass-encoding3.scala | 37 ++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index c6edc4b085a0..af9831ebffa1 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -8,6 +8,7 @@ Extension traits of different kinds cannot be mixed. Extension traits can extend normal traits. + Extension traits can extend extension traits, but only with the same type parameters Normal traits cannot extend extension traits. */ object runtime { @@ -71,6 +72,7 @@ object runtime { - trait / trait with common / extension trait - extending class / extension - monomorphic / generic implementation + - context-bound / direct use of type trait HasLength { def length: Int @@ -160,6 +162,9 @@ object runtime { impl.longest.length < impl.limit } + def length1(x: HasLength) = x.length + def lengthOK1(x: HasBoundedLength) = x.length < x.common.limit + val xs = Array(1, 2, 3) val c1 = new C1(xs) val cg1 = new CG1(xs) @@ -209,6 +214,18 @@ object runtime { longestLengthOK(cg3) longestLengthOK(d3) longestLengthOK(cg3) + + length1(c1) + length1(cg1) + length1(c2) + length1(cg2) + length1(c3) + length1(cg3) + + lengthOK1(c2) + lengthOK1(cg2) + lengthOK1(c3) + lengthOK1(cg3) */ object hasLength { import runtime._ @@ -386,6 +403,9 @@ object hasLength { impl.longest.length < impl.limit } + def length1(x: HasLength) = x.length + def lengthOK1(x: HasBoundedLength) = x.length < x.common.limit + val xs = Array(1, 2, 3) val c1 = new C1(xs) val cg1 = new CG1(xs) @@ -435,6 +455,18 @@ object hasLength { longestLengthOK[CG3[Int]] longestLengthOK[D3] longestLengthOK[DG3[Int]] + + length1(c1) + length1(cg1) + length1(c2) + length1(cg2) + length1(c3) + length1(cg3) + + lengthOK1(c2) + lengthOK1(cg2) + lengthOK1(c3) + lengthOK1(cg3) } /** 1. Simple type classes with monomorphic implementations and direct extensions. @@ -630,8 +662,7 @@ object ord { } } } - - implicit def listOrd[T](implicit $ev: Ord.Impl[T]): ListOrd[T] = + implicit def ListOrd[T](implicit $ev: Ord.Impl[T]): ListOrd[T] = new ListOrd[T] def min[T](x: T, y: T)(implicit $ev: Ord.Impl[T]): T = @@ -649,7 +680,7 @@ object ord { /** 3. Higher-kinded type classes - extension trait Functor[A] extends TypeClass1 { + extension trait Functor[A] { def map[B](f: A => B): This[B] common def pure[A](x: A): This[A] From 153554b140f9a4e54b5dfc46655df1fdf6077c11 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Mar 2018 16:10:02 +0200 Subject: [PATCH 40/54] Treat common traits and type classes alike --- tests/pos/typeclass-encoding3.scala | 167 ++++++++++++++-------------- 1 file changed, 85 insertions(+), 82 deletions(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index af9831ebffa1..a5770c1256ea 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -1,56 +1,59 @@ /* [T : M] = [T] ... (implicit ev: Injectable[T, M]) if M is a normal trait - = [T] ... (implicit ev: M.Impl[T]) if M is an extension trait + = [T] ... (implicit ev: M.Impl[T]) if M is a trait with common members M.Impl[T] = Common { type This = T } M.Impl[T[_]] = Common { type This = T } ... - Extension traits of different kinds cannot be mixed. - Extension traits can extend normal traits. - Extension traits can extend extension traits, but only with the same type parameters - Normal traits cannot extend extension traits. + - An extension trait is a trait that uses This or a trait inheriting an extension trait. + - The kind of `This` is the kind of the extension trait. + - Extension traits of different kinds cannot inherit each other and cannot be mixed using `with`. + - A class implementing extension traits fixes the maning of `This`. + - Such a class cannot be extended by classes that implement other extension traits. */ object runtime { - trait TypeClass { - /** The companion object of the implementing type */ - val `common`: TypeClass.Common - } - trait Injector { + /** The implementating type */ type This + + /** The implemented trait */ type Instance + + /** The implementation via type `T` for this trait */ implicit def inject(x: This): Instance } + trait IdentityInjector extends Injector { + def inject(x: This): Instance = x.asInstanceOf + } + + trait SubtypeInjector[T] extends Injector { + type This = T + type Instance = T + def inject(x: T): T = x + } + type Injectable[T, +U] = Injector { type This = T; type Instance <: U } - implicit def selfInject[U, T <: U]: Injectable[T, U] = - new Injector{ - type This = T - type Instance = U - implicit def inject(x: This): Instance = x - } + def selfInject[U, T <: U]: Injectable[T, U] = new SubtypeInjector[T] {} + + trait TypeClass { + /** The companion object of the implementing type */ + val `common`: TypeClass.Common + } object TypeClass { /** Base trait for companion objects of all implementations of this typeclass */ trait Common extends Injector { self => - - /** The implementating type */ - type This - /** The implemented typeclass */ - type Instance <: TypeClass { val `common`: self.type } - - /** Create instances of typeclass from instances of the implementing type */ - implicit def inject(x: This): Instance + type Instance <: TypeClass } /** Base trait for the companion objects of type classes themselves */ trait Companion { - /** The `Common` base trait defining common (static) operations of this typeclass */ type Common <: TypeClass.Common @@ -62,9 +65,8 @@ object runtime { } } - implicit def injectTypeClass[From, U](x: From) - (implicit ev: Injector { type This = From }): ev.Instance = - ev.inject(x) + implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type This = From }): ev.Instance = + ev.inject(x) } /** 0. All combinations of @@ -235,13 +237,14 @@ object hasLength { def length: Int } - trait HasBoundedLength extends HasLength { + trait HasBoundedLength extends HasLength with TypeClass { val `common`: HasBoundedLength.Common import `common`._ } - object HasBoundedLength { - trait Common { + object HasBoundedLength extends TypeClass.Companion { + trait Common extends TypeClass.Common { + type Instance <: HasBoundedLength def limit: Int } } @@ -252,7 +255,7 @@ object hasLength { } object HasBoundedLengthX extends TypeClass.Companion { - trait Common extends HasBoundedLength.Common with TypeClass.Common { self => + trait Common extends HasBoundedLength.Common with TypeClass.Common { self => type Instance <: HasBoundedLengthX { val `common`: self.type } def limit: Int def longest: This @@ -271,50 +274,46 @@ object hasLength { val `common`: C2Common = C2 import `common`._ } - class C2Common extends HasBoundedLength.Common { + abstract class C2Common extends HasBoundedLength.Common { def limit = 100 } - object C2 extends C2Common + object C2 extends C2Common with SubtypeInjector[C2] class CG2[T](xs: Array[T]) extends CG1(xs) with HasBoundedLength { val `common`: CG2Common[T] = CG2[T] import `common`._ } - class CG2Common[T] extends HasBoundedLength.Common { + abstract class CG2Common[T] extends HasBoundedLength.Common { def limit = 100 } object CG2 { - def apply[T] = new CG2Common[T] + def apply[T] = new CG2Common[T] with SubtypeInjector[CG2[T]] } class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { override val `common`: C3Common = C3 import `common`._ } - class C3Common extends C2Common with HasBoundedLengthX.Common { self => + abstract class C3Common extends C2Common with HasBoundedLengthX.Common { self => type This = C3 type Instance = C3 { val `common`: self.type } - def inject(x: This): Instance = x.asInstanceOf def longest = new C3(new Array[Int](limit)) } - implicit object C3 extends C3Common + object C3 extends C3Common with IdentityInjector class CG3[T](xs: Array[T])(implicit tag: ClassTag[T]) extends CG2(xs) with HasBoundedLengthX { override val `common`: CG3Common[T] = CG3[T] import `common`._ } - class CG3Common[T](implicit tag: ClassTag[T]) extends CG2Common[T] with HasBoundedLengthX.Common { self => + abstract class CG3Common[T](implicit tag: ClassTag[T]) extends CG2Common[T] with HasBoundedLengthX.Common { self => type This = CG3[T] type Instance = CG3[T] { val `common`: self.type } - def inject(x: This): Instance = x.asInstanceOf - def longest = new CG3(new Array[T](limit)) } object CG3 { - def apply[T](implicit tag: ClassTag[T]) = new CG3Common[T] + def apply[T](implicit tag: ClassTag[T]) = new CG3Common[T] with IdentityInjector } - implicit def CG3Common[T](implicit tag: ClassTag[T]): CG3Common[T] = CG3[T] class D1(val xs: Array[Int]) class DG1[T](val xs: Array[T]) @@ -342,7 +341,7 @@ object hasLength { } implicit def DGHasLength[T]: DGHasLength[T] = new DGHasLength - implicit object DHasBoundedLength extends Injector with HasBoundedLength.Common { self => + object DHasBoundedLength extends HasBoundedLength.Common { self => type This = D2 type Instance = HasBoundedLength def inject(x: D2) = new HasBoundedLength { @@ -353,7 +352,7 @@ object hasLength { def limit = 100 } - class DGHasBoundedLength[T] extends Injector with HasBoundedLength.Common { self => + class DGHasBoundedLength[T] extends HasBoundedLength.Common { self => type This = DG2[T] type Instance = HasBoundedLength def inject(x: DG2[T]) = new HasBoundedLength { @@ -395,8 +394,8 @@ object hasLength { def lengthOK[T](x: T)(implicit ev: Injectable[T, HasBoundedLength]) = x.length < x.common.limit - def lengthOKX[T](x: T)(implicit ev: HasBoundedLengthX.Impl[T]) = - x.length < HasBoundedLengthX.impl[T].limit + def lengthOKX[T](x: T)(implicit ev: HasBoundedLength.Impl[T]) = + x.length < HasBoundedLength.impl[T].limit def longestLengthOK[T](implicit ev: HasBoundedLengthX.Impl[T], tag: ClassTag[T]) = { val impl = HasBoundedLengthX.impl[T] @@ -406,7 +405,11 @@ object hasLength { def length1(x: HasLength) = x.length def lengthOK1(x: HasBoundedLength) = x.length < x.common.limit + def ctag[T](implicit tag: ClassTag[T]) = tag + val xs = Array(1, 2, 3) + val intTag = implicitly[ClassTag[Int]] + val c1 = new C1(xs) val cg1 = new CG1(xs) val c2 = new C2(xs) @@ -421,38 +424,38 @@ object hasLength { val d3 = new D3(xs) val dg3 = new DG3(xs) - length(c1) - length(cg1) - length(c2) - length(cg2) - length(c3)(selfInject) - length(cg3)(selfInject) - - length(d1) - length(dg1) - length(d2) - length(dg2) - length(d3) - length(dg3) - - lengthOK(c2) - lengthOK(cg2) - lengthOK(c3)(selfInject) - lengthOK(cg3)(selfInject) - - lengthOK(d2) - lengthOK(dg2) - lengthOK(d3) - lengthOK(dg3) - - lengthOKX(c3) - lengthOKX(cg3) - - lengthOKX(d3) - lengthOKX(dg3) - - longestLengthOK[C3] - longestLengthOK[CG3[Int]] + length(c1)(selfInject) + length(cg1)(selfInject) + length(c2)(C2) + length(cg2)(CG2[Int]) + length(c3)(C3) + length(cg3)(CG3[Int]) + + length(d1)(DHasLength) + length(dg1)(DGHasLength[Int]) + length(d2)(DHasBoundedLength) + length(dg2)(DGHasBoundedLength[Int]) + length(d3)(DHasBoundedLengthX) + length(dg3)(DGHasBoundedLengthX[Int]) + + lengthOK(c2)(C2) + lengthOK(cg2)(CG2[Int]) + lengthOK(c3)(C3) + lengthOK(cg3)(CG3[Int]) + + lengthOK(d2)(DHasBoundedLength) + lengthOK(dg2)(DGHasBoundedLength[Int]) + lengthOK(d3)(DHasBoundedLengthX) + lengthOK(dg3)(DGHasBoundedLengthX[Int]) + + lengthOKX(c3)(C3) + lengthOKX(cg3)(CG3[Int]) + + lengthOKX(d3)(DHasBoundedLengthX) + lengthOKX(dg3)(DGHasBoundedLengthX[Int]) + + longestLengthOK[C3](C3, ctag[C3]) + longestLengthOK[CG3[Int]](CG3[Int], ctag[CG3[Int]]) longestLengthOK[D3] longestLengthOK[DG3[Int]] @@ -716,7 +719,7 @@ object runtime1 { object TypeClass1 { trait Common { self => type This[X] - type Instance[X] <: TypeClass1[X] { val `common`: self.type } + type Instance[X] <: TypeClass1[X] def inject[A](x: This[A]): Instance[A] } @@ -727,7 +730,7 @@ object runtime1 { } } - implicit def decorateTypeClass1[A, From[_]](x: From[A]) + implicit def applyInjector1[A, From[_]](x: From[A]) (implicit ev: TypeClass1.Common { type This = From }): ev.Instance[A] = ev.inject(x) } From 556add727f571cda90075b74cba6ad76595329d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Mar 2018 17:54:41 +0200 Subject: [PATCH 41/54] More examples for typeclass encodings --- tests/pos/typeclass-encoding3.scala | 44 ++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index a5770c1256ea..5378a1275ecb 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -6,6 +6,9 @@ M.Impl[T[_]] = Common { type This = T } ... + - Common definitions in traits may not see trait parameters, but common definitions + in classes may see class parameters. + - An extension trait is a trait that uses This or a trait inheriting an extension trait. - The kind of `This` is the kind of the extension trait. - Extension traits of different kinds cannot inherit each other and cannot be mixed using `with`. @@ -80,11 +83,16 @@ object runtime { def length: Int } + trait Cmp[A] { + def isSimilar(x: A): Boolean + common def exact: Boolean + } + trait HasBoundedLength extends HasLength { common def limit: Int } - extension trait HasBoundedLengthX extends HasBoundedLength { + trait HasBoundedLengthX extends HasBoundedLength { common def longest: This } @@ -96,12 +104,16 @@ object runtime { def length = xs.length } - class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength { + class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength with Cmp[Seq[Int]] { + def isSimilar(x: Seq[Int]) = xs.deep == x common def limit = 100 + common def exact = true } - class CG2[T](xs: Array[Int]) extends CG1[T](xs) with HasBoundedLength { + class CG2[T](xs: Array[Int]) extends CG1[T](xs) with HasBoundedLength with Cmp[Seq[T]] { + def isSimilar(x: Seq[T]) = xs.deep == x common def limit = 100 + common def exact = true } final class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { @@ -237,6 +249,20 @@ object hasLength { def length: Int } + trait Cmp[A] extends TypeClass { + val `common`: HasBoundedLength.Common + import `common`._ + def isSimilar(x: A): Boolean + } + + object Cmp extends TypeClass.Companion { + trait Common extends TypeClass.Common { + type Instance <: HasBoundedLength + def exact: Boolean + } + } + + trait HasBoundedLength extends HasLength with TypeClass { val `common`: HasBoundedLength.Common import `common`._ @@ -264,27 +290,31 @@ object hasLength { class C1(xs: Array[Int]) extends HasLength { def length = xs.length + def isSimilar(x: Seq[Int]) = xs.deep == x } class CG1[T](xs: Array[T]) extends HasLength { def length = xs.length + def isSimilar(x: Seq[T]) = xs.deep == x } - class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength { + class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength with Cmp[Seq[Int]] { val `common`: C2Common = C2 import `common`._ } - abstract class C2Common extends HasBoundedLength.Common { + abstract class C2Common extends HasBoundedLength.Common with Cmp.Common{ def limit = 100 + def exact = true } object C2 extends C2Common with SubtypeInjector[C2] - class CG2[T](xs: Array[T]) extends CG1(xs) with HasBoundedLength { + class CG2[T](xs: Array[T]) extends CG1(xs) with HasBoundedLength with Cmp[Seq[T]] { val `common`: CG2Common[T] = CG2[T] import `common`._ } - abstract class CG2Common[T] extends HasBoundedLength.Common { + abstract class CG2Common[T] extends HasBoundedLength.Common with Cmp.Common { def limit = 100 + def exact = true } object CG2 { def apply[T] = new CG2Common[T] with SubtypeInjector[CG2[T]] From e4beb48d0519709e78f2166acbfedae965cebb44 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 29 Mar 2018 13:31:07 +0200 Subject: [PATCH 42/54] Allow extension re-use through automatically inserted forwarders --- tests/pos/typeclass-encoding3.scala | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index 5378a1275ecb..551a7083d91f 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -512,8 +512,11 @@ object hasLength { common def unit: This } - extension IntOps for Int : Monoid { + extension IntSemiGroup for Int : SemiGroup { def add(that: Int) = this + that + } + + extension IntMonoid for Int : Monoid { common def unit = 0 } @@ -567,13 +570,22 @@ object semiGroups { } } - implicit object IntOps extends Monoid.Common { + implicit object IntSemiGroup extends SemiGroup.Common { self => + type This = Int + type Instance = SemiGroup { val `common`: self.type } + def inject($this: Int) = new SemiGroup { + val `common`: self.type = self + def add(that: Int): Int = $this + that + } + } + + implicit object IntMonoid extends Monoid.Common { self => type This = Int - type Instance = Monoid { val `common`: IntOps.type } + type Instance = Monoid { val `common`: self.type } def unit: Int = 0 def inject($this: Int) = new Monoid { - val `common`: IntOps.this.type = IntOps.this - def add(that: This): This = $this + that + val `common`: self.type = self + def add(that: This): This = IntSemiGroup.inject($this).add(that) } } From 2e754c359a9151be7894a9bd9032ed7ff943150f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 13:22:07 +0200 Subject: [PATCH 43/54] Explain possible typeclass trait extension --- docs/docs/reference/common-declarations.md | 256 ------------------ .../reference/extend/common-declarations.md | 133 +++++++++ .../reference/extend/common-translation.md | 115 ++++++++ docs/docs/reference/extend/context-bounds.md | 175 ++++++++++++ .../reference/extend/extension-methods.md | 2 +- .../reference/extend/factored-instances.md | 76 ++++++ .../reference/extend/instance-declarations.md | 10 + docs/docs/reference/extend/local-coherence.md | 60 ++++ .../extend/parameterized-typeclasses.md | 85 ++++++ docs/docs/reference/extend/translation.md | 11 +- .../docs/reference/extend/typeclass-traits.md | 66 +++++ docs/sidebar.yml | 22 +- tests/pos/typeclass-encoding3.scala | 89 +++--- 13 files changed, 794 insertions(+), 306 deletions(-) delete mode 100644 docs/docs/reference/common-declarations.md create mode 100644 docs/docs/reference/extend/common-declarations.md create mode 100644 docs/docs/reference/extend/common-translation.md create mode 100644 docs/docs/reference/extend/context-bounds.md create mode 100644 docs/docs/reference/extend/factored-instances.md create mode 100644 docs/docs/reference/extend/local-coherence.md create mode 100644 docs/docs/reference/extend/parameterized-typeclasses.md create mode 100644 docs/docs/reference/extend/typeclass-traits.md diff --git a/docs/docs/reference/common-declarations.md b/docs/docs/reference/common-declarations.md deleted file mode 100644 index 6e99b6f73034..000000000000 --- a/docs/docs/reference/common-declarations.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -layout: doc-page -title: "Common Declarations" ---- - -`common` declarations and definitions are a way to specify members of the companion object of a class. Unlike `static` definitions in Java, `common` declarations can be inherited. - -As an example, consider the following trait `Text` with an implementation class `FlatText`. - -```scala -trait Text { - def length: Int - def apply(idx: Int): Char - def concat(txt: Text): Text - def toStr: String - - common def fromString(str: String): Text - common def fromStrings(strs: String*): Text = - ("" :: strs).map(fromString).reduceLeft(_.concat) -} - -class FlatText(str: String) extends Text { - def length = str.length - def apply(n: Int) = str.charAt(n) - def concat(txt: Text): Text = new FlatText(str ++ txt.toStr) - def toStr = str - - common def fromString(str: String) = new FlatText(str) -} -``` - -The `common` method `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: - -```scala -val txt1 = FlatText.fromString("hello") -val txt2 = FlatText.fromStrings("hello", ", world") -``` - -`common` definitions are only members of the companion objectcs of classes, not traits. So the following would give a "member not found" error. - -```scala -val erroneous = Text.fromStrings("hello", ", world") // error: not found -``` - -## The `Instance` type - -In the previous example, the argument and result type of `concat` is just `Text`. So every implementation of `Text` has to be prepared to concatenate all possible implementatons of `Text`. Furthermore, we hide the concrete implementation type in the result type of `concat` and of the construction methods `fromString` and `fromStrings`. Sometimes we want a different design that specifies the actual implementation type instead of the base trait `Text`. We can refer to this type using the predefined type `Instance`: - -```scala -trait Text { - def length: Int - def apply(idx: Int): Char - def concat(txt: Instance): Instance - def toStr: String - - common def fromString(str: String): Instance - common def fromStrings(strs: String*): Instance = - ("" :: strs).map(fromString).reduceLeft(_.concat) -} -``` - -In traits that define or inherit `common` definitions, the `Instance` type refers to the (as yet unknown) instance type whose -companion object implements the trait. To see why `Instance` is useful, consider another possible implementation of `Text`, implemented as a tree of strings. The advantage of the new implementation is that `concat` is constant time. Both old and new implementations share the definition of the `common` method `fromStrings`. - -```scala -enum ConcText extends Text { - case Str(s: String) - case Conc(t1: ConcText, t2: ConcText) - - lazy val length = this match { - case Str(s) => s.length - case Conc(t1, t2) => t1.length + t2.length - } - - def apply(n: Int) = this match { - case Str(s) => s.charAt(n) - case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) - } - - def concat(txt: ConcText) = Conc(this, txt) - - def toStr: String = this match { - case Str(s) => s - case Conc(t1, t2) => t1.toStr ++ t2.toStr - } - common def fromString(str: String): ConcText = Str(str) -} -``` - -The `concat` method of `ConcText` with type `(txt: ConcText): ConcText` is a valid implementation of the -abstract method in `Text` of type `(txt: Instance): Instance` because `ConcText` is a class implementing `Text` -which means that it fixes `Instance` to be `ConcText`. - -Note: The `Instance` type is a useful abstraction for traits that are always implemented via `extends`. For type-class like traits that are intended to be implemented after the fact with extension clauses, there is another predefined type `This` that is generally more appropriate (more on `This` in the typeclass section). - -## The `common` Reference - -Let's add another method to `Text`: - - trait Text { - ... - def flatten: Instance = fromString(toStr) - } - -Why does this work? The `fromString` method is abstract in `Text` so how to we find the correct implementation in `flatten`? -Comparing with the `toStr` reference, this one is an instance method and therefore is expanded to `this.toStr`. But the same does not work for `fromString` because it is a `common` method, not an instance method. -In fact, the application above is syntactic sugar for - - this.common.fromString(this.toStr) - -The `common` selector is defined in each trait that defines or inherits `common` definitions. It refers at runtime to -the object that implements the `common` definitions. - -## Translation - -The translation of a trait `T` that defines `common` declarations `common D1, ..., common Dn` -and extends traits with common declarations `P1, ..., Pn` is as follows: -All `common` declarations are put in a trait `T.Common` which is defined in `T`'s companion object: - - object T { - trait Common extends P1.Common with ... with Pn.Common { self => - type Instance <: T { val `common`: self.type } - D1 - ... - Dn - } - } - -The trait inherits all `Common` traits associated with `T`'s parent traits. If no explicit definition of `Instance` is -given, it declares the `Instance` type as shown above. The trait `T` itself is expanded as follows: - - trait T extends ... { - val `common`: T.Common - import `common`._ - - ... - } - -Any direct reference to `x.common` in the body of `T` is simply translated to - - x.`common` - -The translation of a class `C` that defines `common` declarations `common D1, ..., common Dn` -and extends traits with common declarations `P1, ..., Pn` is as follows: -All `common` definitions of the class itself are placed in `C`'s companion object, which also inherits all -`Common` traits of `C`'s parents. If `C` already defines a companion object, the synthesized parents -come after the explicitly declared ones, whereas the common definitions precede all explicitly given statements of the -companion object. The companion object also defines the `Instance` type as - - type Instance = C - -unless an explicit definition of `Instance` is given in the same object. - -### Example: - -As an example, here is the translation of trait `Text` and its two implementations `FlatText` and `ConcText`: - -```scala -trait Text { - val `common`: Text.Common - import `common`._ - - def length: Int - def apply(idx: Int): Char - def concat(txt: Instance): Instance - def toStr: String - def flatten = `common`.fromString(toStr) -} -object Text { - trait Common { self => - type Instance <: Text { val `common`: self.type } - def fromString(str: String): Instance - def fromStrings(strs: String*): Instance = - ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) - } -} - -class FlatText(str: String) extends Text { - val `common`: FlatText.type = FlatText - import `common`._ - - def length = str.length - def apply(n: Int) = str.charAt(n) - def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) - def toStr = str -} -object FlatText extends Text.Common { - type Instance = FlatText - def fromString(str: String) = new FlatText(str) -} - -enum ConcText extends Text { - val `common`: ConcText.type = ConcText - import `common`._ - - case Str(s: String) - case Conc(t1: Text, t2: Text) - - lazy val length = this match { - case Str(s) => s.length - case Conc(t1, t2) => t1.length + t2.length - } - - def apply(n: Int) = this match { - case Str(s) => s.charAt(n) - case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) - } - - def concat(txt: ConcText): ConcText = Conc(this, txt) - - def toStr: String = this match { - case Str(s) => s - case Conc(t1, t2) => t1.toStr ++ t2.toStr - } -} - -object ConcText extends Text.Common { - type Instance = ConcText - def fromString(str: String) = Str(str) -} -``` - -### Relationship with Parameterization - -Common definitions do not see the type parameters of their enclosing class or trait. So the following is illegal: - -```scala -trait T[A] { - common def f: T[A] -} -``` - -The implicit `Instance` declaration of a trait or class follows in its parameters the parameters of the -trait or class. For instance: - -```scala -trait Sequence[+T <: AnyRef] { - def map[U <: AnyRef](f: T => U): Instance[U] - - common def empty[T]: Instance[T] -} -``` -The implicitly defined `Instance` declaration would be in this case: - -```scala -object Sequence { - trait Common { self => - type Instance[+T <: AnyRef] <: Sequence[T] { val `common`: self.type } - } -} -``` - -The rules for mixing `Instance` definitions of different kinds depend on the status of #4150. If #4150 is -accepted, we permit `Instance` definitions to co-exist at different kinds. If #4150 is not accepted, we -have to forbid this case, which means that a class must have the same parameter structure as all the traits -with common members that it extends. diff --git a/docs/docs/reference/extend/common-declarations.md b/docs/docs/reference/extend/common-declarations.md new file mode 100644 index 000000000000..3575e9330bd9 --- /dev/null +++ b/docs/docs/reference/extend/common-declarations.md @@ -0,0 +1,133 @@ +--- +layout: doc-page +title: "Common Declarations" +--- + +Typeclass traits can have `common` declarations. These are a way to +abstract over values that exist only once for each implementing type. +At first glance, `common` declarations resemble `static` definitions in a language like Java, +but they are more general since they can be inherited. + +As an example, consider the following trait `Text` with an implementation class `FlatText`. + +```scala +trait Text extends TypeClass { + def length: Int + def apply(idx: Int): Char + def concat(txt: This): This + def toStr: String + + common def fromString(str: String): This + common def fromStrings(strs: String*): This = + ("" :: strs).map(fromString).reduceLeft(_.concat) +} + +case class FlatText(str: String) extends Text { + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText): FlatText = FlatText(str ++ txt.str) + def toStr = str + + common def fromString(str: String) = FlatText(str) +} +``` + +The `common` method `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: + +```scala +val txt1 = FlatText.fromString("hello") +val txt2 = FlatText.fromStrings("hello", ", world") +``` + +`common` declarations only define members of the companion objects of classes, not traits. So the following would give a "member not found" error. + +```scala +val erroneous = Text.fromStrings("hello", ", world") // error: not found +``` + +The `common` definition of `fromString` in `FlatText` implicitly defines a member of +`object FlatText`. Alternatively, one could have defined it as a regular method in the +companion object. This is done in the following second implementation of `Text`, which +represents a text as a tree of strings. Both old and new implementations share the definition of the `common` method `fromStrings` in `Text`. + +```scala +enum ConcText { + + case Str(s: String) + case Conc(t1: ConcText, t2: ConcText) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: Text) = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} +object ConcText { + def fromString(str: String): ConcText = Str(str) +} +``` + +## The `common` Reference + +Let's add another method to `Text`: +```scala +trait Text { + ... + def flatten: Instance = fromString(toStr) +} +``` +Why does this work? The `fromString` method is abstract in `Text` so how to we find the correct implementation in `flatten`? +Comparing with the `toStr` reference, that one is an instance method and therefore is expanded to `this.toStr`. But the same does not work for `fromString` because it is a `common` method, not an instance method. +In fact, the application above is syntactic sugar for + +```scala +this.common.fromString(this.toStr) +``` +The `common` selector is defined in each typeclass trait. It refers at runtime to +the object that implements the `common` declarations. + +### Relationship with Parameterization + +There are special rules for common declarations in parameterized traits or classes. Parameterized traits and classes can both contain common declarations, but they have different visibility rules. Common declarations in a trait do _not_ see the type parameters of the enclosing trait. So the following is illegal: + +```scala +trait T[A] { + /*!*/ common def f: T[A] // error: not found: A +} +``` + +On the other hand, common definitions in a class or an extension clause _can_ refer to the type parameters of that class. Consequently, actual type arguments have to be specified when accessing such a common member. Example: + +```scala +extension SetMonoid[T] for Set[T] : Monoid { + def add(that: Set[T]) = this ++ that + common def unit: Set[T] = Set() +} + +Set[Int].unit +Set[Set[String]].unit +``` + +**Note:** Common definitions in a parameterized class `C[T]` cannot be members of the companion object of `C` because that would lose the visibility of the type parameter `T`. Instead they are members of a separate class that is the result type of an `apply` method +in the companion object. For instance, the `SetMonoid` extension above would be expanded +along the following lines: + +```scala +object SetMonoid { + def apply[T] = new Monoid.Common { + def unit: Set[T] = Set() + } +} +``` +Then, as always, `Set[Int].unit` expands to `Set.apply[Int].unit`. diff --git a/docs/docs/reference/extend/common-translation.md b/docs/docs/reference/extend/common-translation.md new file mode 100644 index 000000000000..c5d2719bfdff --- /dev/null +++ b/docs/docs/reference/extend/common-translation.md @@ -0,0 +1,115 @@ + + +## Specification + +> + +## Translation + +Here is one possible translation of a trait `T` that defines `common` declarations `common D1, ..., common Dn` +and extends traits with common declarations `P1, ..., Pn` is as follows: +All `common` declarations are put in a trait `T.Common` which is defined in `T`'s companion object: + + object T { + trait Common extends P1.Common with ... with Pn.Common { self => + type Instance <: T { val `common`: self.type } + D1 + ... + Dn + } + } + +The trait inherits all `Common` traits associated with `T`'s parent traits. If no explicit definition of `Instance` is +given, it declares the `Instance` type as shown above. The trait `T` itself is expanded as follows: + + trait T extends ... { + val `common`: T.Common + import `common`._ + + ... + } + +Any direct reference to `x.common` in the body of `T` is simply translated to + + x.`common` + +The translation of a class `C` that defines `common` declarations `common D1, ..., common Dn` +and extends traits with common declarations `P1, ..., Pn` is as follows: +All `common` definitions of the class itself are placed in `C`'s companion object, which also inherits all +`Common` traits of `C`'s parents. If `C` already defines a companion object, the synthesized parents +come after the explicitly declared ones, whereas the common definitions precede all explicitly given statements of the +companion object. The companion object also defines the `Instance` type as + + type Instance = C + +unless an explicit definition of `Instance` is given in the same object. + +### Example: + +As an example, here is the translation of trait `Text` and its two implementations `FlatText` and `ConcText`: + +```scala +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Instance): Instance + def toStr: String + def flatten = `common`.fromString(toStr) +} +object Text { + trait Common { self => + type Instance <: Text { val `common`: self.type } + def fromString(str: String): Instance + def fromStrings(strs: String*): Instance = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val `common`: FlatText.type = FlatText + import `common`._ + + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + type Instance = FlatText + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val `common`: ConcText.type = ConcText + import `common`._ + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: ConcText): ConcText = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + type Instance = ConcText + def fromString(str: String) = Str(str) +} +``` + diff --git a/docs/docs/reference/extend/context-bounds.md b/docs/docs/reference/extend/context-bounds.md new file mode 100644 index 000000000000..6d2567a9763b --- /dev/null +++ b/docs/docs/reference/extend/context-bounds.md @@ -0,0 +1,175 @@ +--- +layout: doc-page +title: "Context Bounds" +--- + +Context bounds present a new, general way to abstract over extensions, encompassing both class inheritance and instance declarations. Previously, a context +bound `[T : B]` required a parameterized class `B` as the bound of the type parameter `T`. +It specified an implicit evidence parameter of type `B[T]`. For instance, +```scala + def f[T : math.Ordering] +``` +was a shorthand form for +```scala + def f[T](implicit $ev: math.Ordering[T]) +``` + +This usage pattern is still supported. But there is now a second usage of context bounds which takes effect if the kind of the type parameter `T` and the bound `B` is the same. + +### Typeclass Bounds + +If `B` is a typeclass trait, a context bound `[T : B]` means "the implementation of typeclass `B` by type `T`". It translates to an implicit evidence parameter of type +`B.Impl[T]`. Example: + +```scala +def sum[T: Monoid](xs: Iterable[T]): T = + xs.foldLeft(Monoid.impl[T].unit)(_ `add` _) +``` + +This definition of `sum` can be used for all `Monoid` implementations, no matter whether they are defined by extending classes or extension clauses: + +```scala +assert( sum(List(1, 2, 3)) == 6 ) +assert( sum(List(Set(1, 2), Set(3))) == Set(1, 2, 3) ) +assert( sum(List(S(Z), S(S(Z)))) == List(S(S(S(Z)))) ) +``` + +Here is an alternative formulation of `sum` which makes the implicit evidence parameter explicit: + +```scala +def sum[T](xs: Iterable[T])(implicit ev: Monoid.Impl[T]): T = + xs.foldLeft(ev.unit)(_ `add` _) +``` + +Of course, we could also have defined `sum` as an extension method: + +```scala +extension MonoidSum[T : Monoid] for Iterable[T] { + def sum = foldLeft(Monoid.impl[T].unit)(_ `add` _) +} +List(1, 2, 3).sum +``` + +### The `Impl` Type. + +The companion object of every typeclass trait `TC` defines a type constructor +```scala +object TC { + type Impl[T] = ... // (implementation dependent) +} +``` +The type `TC.Impl[T]` refers to the implementation object that implements `TC` for `T`. For instance, `Monoid.Impl[Int]` refers to the the `IntMonoid` object, whereas +`Monoid.Impl[Nat]` refers to the companion object of class `Nat`. In general, `TC.Impl[T]` is one of the following: + + - if the implementation of `C : TC` is via an extension clause, the object representing the extension + - if the implementation of `C : TC` is a class `C` extending `TC`, the companion object of `C`. + +Besides the `Impl` type, the companion object of a typeclass trait also defines a utility +method `impl`, specified as follows: +```scala + def impl[T](implicit ev: Impl[T]): Impl[T] = ev +``` +This method is used in the the first implementation of `sum` above to retrieve +the monoid's `unit` value: +```scala +def sum[T: Monoid](xs: Iterable[T]): T = + xs.foldLeft(Monoid.impl[T].unit)(_ `add` _) +``` + +### Injectors + +This explains how `sum` can access `unit`, but what about `add`? The receiver type of `add` +is `Monoid`. How do we get an instance of this type from the implementation type `T`? + +In fact every `TC.Impl[T]` implicit also gives rise to an implicit conversion from `T` to +`TC`. This conversion can be inserted on the left-hand side argument of `add` (which is of type `T`) to produce a `Monoid` instance. (Implementations are free to optimize this, for instance by calling a binary version of `add` directly, which avoids the boxing receiver conversion). + +The details of the conversion are as follows: + + 1. The `scala` package defines an `Injector` trait as follows: + + ```scala + trait Injector { + /** The implementing type */ + type This + + /** The implemented trait */ + type $Instance + + /** The implementation via type `T` for this trait */ + implicit def inject(x: This): $Instance + } + ``` + + The `This` type of this injector is the same as the `This` type of an implementing typeclass trait; it representing the implementing type. The `$Instance` type is name-mangled and therefore should not be referred to in user programs. It refers to the implemented typeclass instance. An `inject` method converts from the first to the second. + + 2. Every implementation object `TC.Impl[T]` inherits an `Injector` instance where `This = T` and `$Instance <: TC`. Hence, it defines an `inject` conversion from `T` to `TC`. + + 3. There is a globally visible implicit conversion defined as follows: + + ```scala + implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type This = From }): ev.$Instance = + ev.inject(x) + ``` +Thus, the fully spelled-out body of the `sum` method is +```scala + def sum[T](xs: Iterable[T])(implicit ev: Monoid.Impl[T]): T = + xs.foldLeft(ev.unit)((x, y) => applyInjector(x)(ev).add(y)) +``` + +### Other Context Bounds + +Context bounds `[T : B]` can also be written if the bound `B` is not a typeclass trait. Still assuming that `T` and `B` have the same kind, the context bound then amounts to +specifying an implicit evidence of type `Injectable[T, B]`, where `scala.Injectable` is defined as follows: +```scala + type Injectable[T, +U] = Injector { type This = T; type $Instance <: U } +``` +Instance declarations of a non-typeclass trait `B` with an implementation type `I` generate implementation objects that inherit from `Injectable[I, B]`. The type bound `[I: B]` is then resolved as the implementation object, exactly in the same way as for typeclass traits. + +If class `C` extends `B` directly, the context bound `[C : B]` is resolved to `selfInject[C, B]`, referring to the following global definition of an injector that works for all pairs of types that are in a subtype relation: +```scala + def selfInject[U, T <: U]: Injectable[T, U] = new Injector { + type This = T + type $Instance = U + def inject(x: T) = x + } +``` +In other words, context bounds [T: B] for non-typeclass traits `B` work similar to the previous (now deprecated) view bounds [T <% B] in that they specify an implicit conversion between `T` and `B`. + +### Conditional Instance Declarations + +Context bounds enable conditional instance declarations. For instance, here is a type class +trait expressing an ordering: + +```scala +trait Ord extends TypeClass { + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + def <= (that: This) = compareTo(that) <= 0 + def >= (that: This) = compareTo(that) >= 0 +} +``` +Here is an instance declaration of `Ord` for `Int`: + +```scala +extension IntOrd for Int : Ord { + def compareTo(that: Int) = + if (this < that) -1 else if (this > that) +1 else 0 +} +``` + +And here is an instance declaration specifying that `List`s are ordered if their elements are: + +```scala +extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _ ) => -1 + case (_ , Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } +} +``` diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index c4947391e513..43bbceb1f063 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -100,7 +100,7 @@ There can be only one parameter clause following a type pattern and it must be i ### Toplevel Type Variables -A type pattern consisting of a top-level typevariable introduces a fully generic extension. For instance, the following extension introduces `x ~ y` as an alias +A type pattern consisting of a top-level type variable introduces a fully generic extension. For instance, the following extension introduces `x ~ y` as an alias for `(x, y)`: ```scala diff --git a/docs/docs/reference/extend/factored-instances.md b/docs/docs/reference/extend/factored-instances.md new file mode 100644 index 000000000000..fbed41c2468a --- /dev/null +++ b/docs/docs/reference/extend/factored-instances.md @@ -0,0 +1,76 @@ +--- +layout: doc-page +title: "Factored Instance Declarations" +--- + +Sometimes we want to re-use parts of one instance declaration in another. As an example, +let's flesh out the `Functor` - `Monad` example, adding a typeclass trait `Applicative` +for applicative functors in-between. + +```scala +trait Functor[A] extends TypeClass { + def map[B](f: A => B): This[B] +} + +trait Applicative[A] extends Functor[A] { + def map2[B, C](that: This[B])(f: (A, B) => C): This[C] + common def pure[B](x: B): This[B] + + def mapWith[B](f: This[A => B]): This[B] = map2(f)((x, f) => f(x)) + def map[B](f: A => B): This[B] = mapWith(pure(f)) +} + +trait Monad[A] extends Applicative[A] { + def flatMap[B](f: A => This[B]): This[B] +} +``` +`Applicative` can be extended in other ways as well. Here is `Traverse`, another common sub-trait of `Applicative`: + +```scala +trait Traverse[A] extends Applicative[A] { + def traverse[B, C[_]: Applicative](f: A => C[B]): C[This[B]] +} +``` + +The `List` type can be made an instance of both `Monad` and `Traverse` by writing an instance declaration for `List[T] : Monad[T]` and another one for `List[T] : Traverse[T]`. +However, these two declarations would then need to duplicate the definitions of the `Applicative` methods `map2` and `pure`. + +Factored instance declarations provide a way to avoid the duplication. The idea is to write three instance declarations, for `Applicative`, `Monad`, and `Traverse`: + +```scala +extension ListApplicative[A] for List[A]: Applicative[A] { + + def map[B](f: A => B): List[B] = this.map(f) + + def map2[B, C](that: List[B])(f: (A, B) => C): List[C] = + for (x <- this; y <- that) yield f(x, y) + + def pure[B]: List[B] = Nil +} + +extension ListMonad[A] for List[A] : Monad[A] { + + def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) +} + +extension ListTraverse[A] for List[A] : Traverse[A] { + + def traverse[B, C[_]: Applicative](f: A => C[B]): C[List[B]] = this match { + case Nil => Applicative.impl[C].pure[List[B]] + case x :: xs => f(x).map2(xs.traverse(f))(_ :: _) + } +} +``` +In the definitions above, `ListMonad` and `ListTraverse` lack definitions for `map2` and `pure`. These definitions are provided implicitly by forwarding to corresponding definitions in the `ListApplicative` instance declaration. If we had written the forwarders for `ListMonad` explicitly, this would look like the following, alternative definition: + +```scala +extension ListMonad[A] for List[A] : Monad[A] { + def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) + + // The following can be implicitly inserted: + def pure[B] = ListApplicative.pure[B] + def map2[B, C](that: List[B])(f: (A, B) => C): List[C] = + ListApplicative.inject(this).map2(that)(f) +} +``` +The last `map2` implementation looks like it requires an expensive object creation, but compilers or runtimes can usually optimize that away using inlining and escape analysis. \ No newline at end of file diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md index 2ab5d3883c11..7d7fdd544b13 100644 --- a/docs/docs/reference/extend/instance-declarations.md +++ b/docs/docs/reference/extend/instance-declarations.md @@ -50,6 +50,16 @@ extension HasEqlImpl[T : Eql] for T : HasEql[T] { } ``` +### Rules for Instance Declarations + +An instance declaration can only implement a single trait, not a combination of traits, nor a class. Furthermore, +the implemented trait must be an interface trait according to the following definition: + +**Definition** An _interface trait_ is a trait that + - has only types and methods as members, + - does not have initialization statements, and + - has only interface traits as parents. + ### Syntax of Extensions The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) diff --git a/docs/docs/reference/extend/local-coherence.md b/docs/docs/reference/extend/local-coherence.md new file mode 100644 index 000000000000..5ddbfc39a80e --- /dev/null +++ b/docs/docs/reference/extend/local-coherence.md @@ -0,0 +1,60 @@ +--- +layout: doc-page +title: "Local Coherence" +--- + +In the context of instance declarations, global coherence means that for every type `I` and typeclass trait `TC` there is never more than one way in which `I` implements `TC`. +Scala does not have a global coherence requirement for its implicits. Instead it reports an ambiguity error if an implicit search yields several different results. The advantage of Scala's approach is its flexibility, in particular its support scoped implicits. But sometimes the ambiguity rules get in the way. Consider for instance the typeclass trait hierarchy presented in the last section, which involves `Functor`, `Applicative`, `Monad`, and `Traverse`. Now add a function like `transform` below that requires its parameter type +to be both a `Monad` and a `Traverse`. +```scala +def transform[F[_] : Monad : Traverse](x: F[A], f: A => A) = { ... x.map(f) ... } +``` +The call to `map` in the body of that function would be in error, because it is ambiguous - we don't know whether we should call the `map` method defined in `Monad` or +the one defined in `Traverse`. To see this in more detail, expand the context bounds in `transform` and add a type ascription `x`: + +```scala +def transform[F[_], A](x: F[A], f: A => A)( + implicit + impl1: Monad.Impl[F], + impl2: Traversable.Impl[F]) + = { ... (x: Functor[A]).map(f) ... } +``` +There are two ways `x` can be converted to a `Functor[A]`, one through `impl1.inject` +the other through `impl2.inject`. Hence, an ambiguity error is reported. +This problem was brought up in issue #2029 and was presented +in a [paper at the Scala Symposium 2017](https://adelbertc.github.io/publications/typeclasses-scala17.pdf). + +Note that if we assume `List` as the actual implementation, this ambiguity is a false negative, since `List`'s implementation of `map` is the same for `Monad` and `Traverse`, +being defined in the common extension `Applicative`. But in general, a type might well have two different implementations for `map` (or `Functor` methods in general) when seen as +a `Monad` or as a `Traverse`. + +The (as yes tentative) idea to solve the problem is to allow a way to constrain implicit values to have common implementations for some of their super-traits. + +This can be done by introducing a predefined type constructor `Super[_]` as a member of all implementation objects of typeclass traits. If `impl` is an implementation object for type `I` and typeclass trait `T`, and `U` is a typeclass trait extended by `T`, then `impl.Super[U]` would give the type of the implementation object from which `impl` obtains all +implementations of `U`. For instance, assuming the factored instance declarations in the last section, +```scala + ListMonad.Super[Functor] = ListApplicative.type + ListTraverse.Super[Functor] = ListApplicative.type +``` +Indeed, all `Functor` methods defined by `List` are defined in `ListApplicative`, and inherited (i.e. implemented implicitly as forwarders) in both `ListMonad` and `ListTraverse`. +On the other hand, if we had not used factorization for `ListMonad`, +`ListMonad.Super[Functor]` would be `ListMonad.type`, since it would be `ListMonad` that +defines some of the methods in `Functor`. + +Once we have the `Super` type defined like this, we can add typeclass constraints to enforce equalities. For instance: + +```scala +def transform[F[_], A](x: F[A], f: A => A)( + implicit + impl1: Monad.Impl[F], + impl2: Traversable.Impl[F], + impl1.Super[Functor] =:= impl2.Super[Functor]) + = { ... (x: Functor[A]).map(f) ... } +``` +Note that this construct involves parameter dependencies as implemented in #2079 - the third implicit parameter contains types that depend on the first two. + +The final piece of the puzzle is to make ambiguity checking take such constraints into account. More precisely, if searching for a injection from a type `T` to a typeclass +`U`, if there are two injectors `i1` and `i2` from `T` to `U`, but `i1.Super[U] =:= i2.Super[U]`, make an arbitrary choice instead of signaling an ambiguity. With this rule, the +`x.map(f)` call in the `transform` method typechecks. + +In effect we have replaced global coherence by local coherence - the ability to detect and require that two implementations implement a typeclass trait in the same way. \ No newline at end of file diff --git a/docs/docs/reference/extend/parameterized-typeclasses.md b/docs/docs/reference/extend/parameterized-typeclasses.md new file mode 100644 index 000000000000..d0273f2316c4 --- /dev/null +++ b/docs/docs/reference/extend/parameterized-typeclasses.md @@ -0,0 +1,85 @@ +--- +layout: doc-page +title: "Parameterized Typeclass Traits" +--- + +Typeclass traits can be parameterized. Example: + +```scala +trait Functor[A] extends TypeClass { + def map[B](f: A => B): This[B] +} + +trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = flatMap(f.andThen(pure)) + + common def pure[A](x: A): This[A] +} +``` +This defines a parameterized typeclass trait `Functor`, extended by a parameterized typeclass trait `Monad`. + +In a parameterized typeclass trait, the kind of `This` is the same as the kind of the +trait. For instance, since `Functor` takes a single invariant type parameter, so does +`This` in `Functor`. Parameterized typeclass traits can only be extended by traits of the same kind. A sub-trait has to pass its type parameters to its super-trait +in the order they were defined: Examples: + +```scala +trait S[A, +B] extends TypeClass +trait T[X, +Y] extends S[X, Y] // OK + +/*!*/ trait T1[X, Y] extends S[X, Y] // error: wrong variance +/*!*/ trait T2[X, Y] extends S[Y, X] // error: wrong parameter order +/*!*/ trait T3[X, Y] extends S[X, Int] // error: not all parameters are passed +``` +These restrictions ensure that all participants in a typeclass trait hierarchy have the same notion of `This`. + +Other types are adapted analogously to higher-kinded types. For instance, `Monad` +defines the `Impl` type so that it takes a higher-kinded type parameter +```scala +type Impl[T[_]] +``` +and its `inject` has the signature given below. +```scala +def inject[A](x: This[A]): $Instance[A] +``` + +Here is an example of an instance declaration implementing a parameterized typeclass trait. It turns `List` into `Monad`. + +```scala +extension ListMonad[A] for List : Monad { + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + common def pure[A] = Nil +} +``` + +Implementations of higher-kinded typeclass traits have to have the same type parameters as the traits they implement, but can also can introduce new type parameters. New type parameters have to follow the type parameters passed along to the typeclass traits. Examples: + +```scala +trait T[X, +Y] { + def unit[A, B: This[A, B] +} +class C[X, +Y, Z] extends T[X, Y] // OK + +extension E1[X, +Y] for I[X, Y] : T[X, Y] // OK +extension E2[X, +Y, Z] for I[X, Y, Z] : T[X, Y] // OK +``` +Implementations are mapped to companions in the usual way after +subtracting any type parameters that are passed on to a parameterized typeclass traits. So the following three expressions would each select a `unit` value of trait `T`: +```scala + C[String].unit + E1.unit + E2[Int].unit +``` + +Context bounds also extend to higher-kinds. For instance, here is an extension method `flatten` for all types implementing the `Monad` trait: + +```scala +extension MonadFlatten[T[_] : Monad] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) +} +``` + diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md index 0a248018cc3e..35b29601c8e4 100644 --- a/docs/docs/reference/extend/translation.md +++ b/docs/docs/reference/extend/translation.md @@ -3,7 +3,14 @@ layout: doc-page title: "Translation of Extensions" --- -Extensons are closely related to implicit classes and can be translated into them. In short, +**Note:** This section gives an explanation how extension methods and instance declarations could be mapped to value classes and implicit classes. It does not consider typeclass traits. Given that value classes and implicit classes are supposed to be replaced by +extension methods and instance declarations, it would be good to present a translation that bypasses value classes and that also extends to typeclass traits. There are several +possibilities for such a translation. We should to experiment further to clarify which +scheme to prefer and flesh it out in detail. + +--- + +Extensions are closely related to implicit classes and can be translated into them. In short, an extension that just adds extension methods translates into an implicit value class whereas an instance declaration translates into a regular implicit class. The following sections sketch this translation. Conversely, it is conceivable (and desirable) to replace most usages of implicit classes and value classes by extensions and [opaque types](../opaques.html). We plan to [drop](../dropped/implicit-value-classes.html) @@ -20,7 +27,7 @@ Assume an extension where both `` and `` can be absent. For simplicity assume that there are no context bounds on any of the type parameters -in ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. +in ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence parameters in ``. The extension is translated to the following implicit value class: diff --git a/docs/docs/reference/extend/typeclass-traits.md b/docs/docs/reference/extend/typeclass-traits.md new file mode 100644 index 000000000000..91630869b400 --- /dev/null +++ b/docs/docs/reference/extend/typeclass-traits.md @@ -0,0 +1,66 @@ +--- +layout: doc-page +title: "Typeclass Traits" +--- + +Typeclass traits offer two new ways to express the type structure of implementing classes. First, they allow to refer to the implementation type using `This`. Second, they can abstract not only over _instance values_ but also over values that exist only once for each implementing _type_, using "common" definitions. + +A typeclass trait is a trait that extends directly or indirectly the marker trait `scala.TypeClass`. Here are two typeclass traits: +```scala +trait SemiGroup extends TypeClass { + def add(that: This) +} +trait Monoid extends SemiGroup { + common def unit: This +} +``` +The `SemiGroup` trait refers to the implementation type via `This`. The implementation type can be +an extending class or it can be defined by an instance declaration. Here is an example of the latter: + +```scala +extension IntMonoid for Int : Monoid { + def add(that: Int) = this + that + common def unit = 0 +} +``` +In this extension, the `This` type of `SemiGroup` and `Monoid` is bound to `Int`. + +Here is an example a class extending `Monoid` directly: + +```scala +enum Nat extends Monoid { + case Z + case S(n: Nat) + + def add(that: Nat): Nat = this match { + case Z => that + case S(n) => S(n.add(that)) + } + + common def unit = Z +} +``` + +In this enum, the `This` type of `SemiGroup` and `Monoid` is bound to `Nat`. + +### The `This` type. + +The `This` type can be seen as an analogue of the `this` value. Where the `this` value refers to the "current object" on which some operation is performed, the `This` type refers to the type of that object. Both versions work for implementing classes and for instance declarations. + +Note that the first class extending a typeclass trait fixes the meaning of `This`. For instance, the following is legal: + +```scala +class A extends SemiGroup { + def add(that: A) = ??? +} +class B extends A with Monoid { + common def unit: B = ??? // OK since B <: A = This +} +``` +But we could not have re-implemented `add` in `B` with +```scala + def add(that: B): B = ... +``` +since `This` is already fixed to be `A`. + +`This` can be thought of as an abstract type member of a typeclass trait. While it is possible to constrain or bind `This` explicitly, this is not encouraged because it might conflict with the compiler-generated bindings of `This`. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 91993880003f..57b0a09f36e3 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -29,6 +29,26 @@ sidebar: url: docs/reference/dependent-function-types.html - title: Literal Singleton Types url: docs/reference/singleton-types.html + - title: Extension Clauses + subsection: + - title: Extension Methods + url: docs/reference/extend/extension-methods.html + - title: Instance Declarations + url: docs/reference/extend/instance-declarations.html + - title: Typeclass Traits + url: docs/reference/extend/typeclass-traits.html + - title: Common Declarations + url: docs/reference/extend/common-declarations.html + - title: Context Bounds + uril: docs/reference/extend/context-bounds.html + - title: Parameterized Typeclasses + url: docs/reference/extend/parameterized-typeclasses.html + - title: Factored Instances + url: docs/reference/extend/factored-instances.html + - title: Local Coherence + url: docs/reference/extend/local-coherence.html + - title: Translation + url: docs/reference/extend/translation.html - title: Enums subsection: - title: Enumerations @@ -43,8 +63,6 @@ sidebar: url: docs/reference/multiversal-equality.html - title: Trait Parameters url: docs/reference/trait-parameters.html - - title: Common Declarations - url: docs/reference/common-declarations.html - title: Inline url: docs/reference/inline.html - title: Meta Programming diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index 551a7083d91f..f01ffde0cc21 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -9,11 +9,10 @@ - Common definitions in traits may not see trait parameters, but common definitions in classes may see class parameters. - - An extension trait is a trait that uses This or a trait inheriting an extension trait. - - The kind of `This` is the kind of the extension trait. - - Extension traits of different kinds cannot inherit each other and cannot be mixed using `with`. - - A class implementing extension traits fixes the maning of `This`. - - Such a class cannot be extended by classes that implement other extension traits. + - A typeclass trait is a trait inheriting directly or indirectly from `scala.TypeClass`. + - A typeclass trait `T` can be only extended by traits and classes that have the same number of type + parameters as `T`, with the same variances. + - The first class implementing a typeclass trait fixes the maning of `This`. */ object runtime { @@ -22,23 +21,23 @@ object runtime { type This /** The implemented trait */ - type Instance + type $Instance /** The implementation via type `T` for this trait */ - implicit def inject(x: This): Instance + implicit def inject(x: This): $Instance } trait IdentityInjector extends Injector { - def inject(x: This): Instance = x.asInstanceOf + def inject(x: This): $Instance = x.asInstanceOf } trait SubtypeInjector[T] extends Injector { type This = T - type Instance = T + type $Instance = T def inject(x: T): T = x } - type Injectable[T, +U] = Injector { type This = T; type Instance <: U } + type Injectable[T, +U] = Injector { type This = T; type $Instance <: U } def selfInject[U, T <: U]: Injectable[T, U] = new SubtypeInjector[T] {} @@ -52,7 +51,7 @@ object runtime { /** Base trait for companion objects of all implementations of this typeclass */ trait Common extends Injector { self => /** The implemented typeclass */ - type Instance <: TypeClass + type $Instance <: TypeClass } /** Base trait for the companion objects of type classes themselves */ @@ -68,7 +67,7 @@ object runtime { } } - implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type This = From }): ev.Instance = + implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type This = From }): ev.$Instance = ev.inject(x) } @@ -257,7 +256,7 @@ object hasLength { object Cmp extends TypeClass.Companion { trait Common extends TypeClass.Common { - type Instance <: HasBoundedLength + type $Instance <: HasBoundedLength def exact: Boolean } } @@ -270,7 +269,7 @@ object hasLength { object HasBoundedLength extends TypeClass.Companion { trait Common extends TypeClass.Common { - type Instance <: HasBoundedLength + type $Instance <: HasBoundedLength def limit: Int } } @@ -282,7 +281,7 @@ object hasLength { object HasBoundedLengthX extends TypeClass.Companion { trait Common extends HasBoundedLength.Common with TypeClass.Common { self => - type Instance <: HasBoundedLengthX { val `common`: self.type } + type $Instance <: HasBoundedLengthX { val `common`: self.type } def limit: Int def longest: This } @@ -326,7 +325,7 @@ object hasLength { } abstract class C3Common extends C2Common with HasBoundedLengthX.Common { self => type This = C3 - type Instance = C3 { val `common`: self.type } + type $Instance <: C3 { val `common`: self.type } def longest = new C3(new Array[Int](limit)) } @@ -338,7 +337,7 @@ object hasLength { } abstract class CG3Common[T](implicit tag: ClassTag[T]) extends CG2Common[T] with HasBoundedLengthX.Common { self => type This = CG3[T] - type Instance = CG3[T] { val `common`: self.type } + type $Instance <: CG3[T] { val `common`: self.type } def longest = new CG3(new Array[T](limit)) } object CG3 { @@ -356,7 +355,7 @@ object hasLength { implicit object DHasLength extends Injector { type This = D1 - type Instance = HasLength + type $Instance = HasLength def inject(x: D1) = new HasLength { def length = xs.length } @@ -364,7 +363,7 @@ object hasLength { class DGHasLength[T] extends Injector { type This = DG1[T] - type Instance = HasLength + type $Instance = HasLength def inject(x: DG1[T]) = new HasLength { def length = xs.length } @@ -373,7 +372,7 @@ object hasLength { object DHasBoundedLength extends HasBoundedLength.Common { self => type This = D2 - type Instance = HasBoundedLength + type $Instance = HasBoundedLength def inject(x: D2) = new HasBoundedLength { val `common`: self.type = self import `common`._ @@ -384,7 +383,7 @@ object hasLength { class DGHasBoundedLength[T] extends HasBoundedLength.Common { self => type This = DG2[T] - type Instance = HasBoundedLength + type $Instance = HasBoundedLength def inject(x: DG2[T]) = new HasBoundedLength { val `common`: self.type = self import `common`._ @@ -396,7 +395,7 @@ object hasLength { implicit object DHasBoundedLengthX extends HasBoundedLengthX.Common { self => type This = D3 - type Instance = HasBoundedLengthX { val `common`: self.type } + type $Instance = HasBoundedLengthX { val `common`: self.type } def inject(x: D3) = new HasBoundedLengthX { val `common`: self.type = self import `common`._ @@ -408,7 +407,7 @@ object hasLength { class DGHasBoundedLengthX[T](implicit tag: ClassTag[T]) extends HasBoundedLengthX.Common { self => type This = DG3[T] - type Instance = HasBoundedLengthX { val `common`: self.type } + type $Instance = HasBoundedLengthX { val `common`: self.type } def inject(x: DG3[T]) = new HasBoundedLengthX { val `common`: self.type = self import `common`._ @@ -503,12 +502,12 @@ object hasLength { } /** 1. Simple type classes with monomorphic implementations and direct extensions. - extension trait SemiGroup { + trait SemiGroup extends TypeClass { def add(that: This): This def add2(that: This): This = add(that).add(that) } - extension trait Monoid extends SemiGroup { + trait Monoid extends SemiGroup { common def unit: This } @@ -530,7 +529,7 @@ object hasLength { case S(n: Nat) def add(that: Nat): Nat = this match { - case S => that + case Z => that case S(n) => S(n.add(that)) } } @@ -539,7 +538,7 @@ object hasLength { } def sum[T: Monoid](xs: List[T]): T = - (Monod.impl[T].unit /: xs)(_ `add` _) + (Monoid.impl[T].unit /: xs)(_ `add` _) */ import runtime._ @@ -555,7 +554,7 @@ object semiGroups { object SemiGroup extends TypeClass.Companion { trait Common extends TypeClass.Common { self => - type Instance <: SemiGroup { val `common`: self.type } + type $Instance <: SemiGroup { val `common`: self.type } } } @@ -565,14 +564,14 @@ object semiGroups { } object Monoid extends TypeClass.Companion { trait Common extends SemiGroup.Common { self => - type Instance <: Monoid { val `common`: self.type } + type $Instance <: Monoid { val `common`: self.type } def unit: This } } implicit object IntSemiGroup extends SemiGroup.Common { self => type This = Int - type Instance = SemiGroup { val `common`: self.type } + type $Instance = SemiGroup { val `common`: self.type } def inject($this: Int) = new SemiGroup { val `common`: self.type = self def add(that: Int): Int = $this + that @@ -581,7 +580,7 @@ object semiGroups { implicit object IntMonoid extends Monoid.Common { self => type This = Int - type Instance = Monoid { val `common`: self.type } + type $Instance = Monoid { val `common`: self.type } def unit: Int = 0 def inject($this: Int) = new Monoid { val `common`: self.type = self @@ -591,7 +590,7 @@ object semiGroups { implicit object StringOps extends Monoid.Common { type This = String - type Instance = Monoid { val `common`: StringOps.type } + type $Instance = Monoid { val `common`: StringOps.type } def unit = "" def inject($this: String) = new Monoid { val `common`: StringOps.this.type = StringOps.this @@ -612,7 +611,7 @@ object semiGroups { } object Nat extends Monoid.Common { type This = Nat - type Instance = Nat + type $Instance = Nat def unit = Nat.Z def inject($this: Nat) = $this } @@ -630,7 +629,7 @@ object semiGroups { /** 2. Generic implementations of simple type classes. - extension trait Ord { + trait Ord extends TypeClass { def compareTo(that: This): Int def < (that: This) = compareTo(that) < 0 def > (that: This) = compareTo(that) > 0 @@ -673,14 +672,14 @@ object ord { } object Ord extends TypeClass.Companion { trait Common extends TypeClass.Common { self => - type Instance <: Ord { val `common`: self.type } + type $Instance <: Ord { val `common`: self.type } def minimum: This } } implicit object IntOrd extends Ord.Common { type This = Int - type Instance = Ord { val `common`: IntOrd.type } + type $Instance = Ord { val `common`: IntOrd.type } val minimum: Int = Int.MinValue def inject($this: Int) = new Ord { val `common`: IntOrd.this.type = IntOrd.this @@ -692,7 +691,7 @@ object ord { class ListOrd[T](implicit $ev: Ord.Impl[T]) extends Ord.Common { self => type This = List[T] - type Instance = Ord { val `common`: self.type } + type $Instance = Ord { val `common`: self.type } def minimum: List[T] = Nil def inject($this: List[T]) = new Ord { val `common`: self.type = self @@ -725,7 +724,7 @@ object ord { /** 3. Higher-kinded type classes - extension trait Functor[A] { + trait Functor[A] extends TypeClass { def map[B](f: A => B): This[B] common def pure[A](x: A): This[A] @@ -736,7 +735,7 @@ object ord { if (n == 0) Functor.impl[F].pure[A] else develop[A, F](n - 1, f).map(f) - extension trait Monad[A] extends Functor[A] { + trait Monad[A] extends Functor[A] { def flatMap[B](f: A => This[B]): This[B] def map[B](f: A => B) = this.flatMap(f.andThen(pure)) } @@ -761,8 +760,8 @@ object runtime1 { object TypeClass1 { trait Common { self => type This[X] - type Instance[X] <: TypeClass1[X] - def inject[A](x: This[A]): Instance[A] + type $Instance[X] <: TypeClass1[X] + def inject[A](x: This[A]): $Instance[A] } trait Companion { @@ -773,7 +772,7 @@ object runtime1 { } implicit def applyInjector1[A, From[_]](x: From[A]) - (implicit ev: TypeClass1.Common { type This = From }): ev.Instance[A] = + (implicit ev: TypeClass1.Common { type This = From }): ev.$Instance[A] = ev.inject(x) } import runtime1._ @@ -787,7 +786,7 @@ object functors { } object Functor extends TypeClass1.Companion { trait Common extends TypeClass1.Common { self => - type Instance[X] <: Functor[X] { val `common`: self.type } + type $Instance[X] <: Functor[X] { val `common`: self.type } def pure[A](x: A): This[A] } } @@ -800,7 +799,7 @@ object functors { } object Monad extends TypeClass1.Companion { trait Common extends Functor.Common { self => - type Instance[X] <: Monad[X] { val `common`: self.type } + type $Instance[X] <: Monad[X] { val `common`: self.type } } } @@ -810,7 +809,7 @@ object functors { implicit object ListMonad extends Monad.Common { type This = List - type Instance[X] = Monad[X] { val `common`: ListMonad.type } + type $Instance[X] = Monad[X] { val `common`: ListMonad.type } def pure[A](x: A) = x :: Nil def inject[A]($this: List[A]) = new Monad[A] { val `common`: ListMonad.this.type = ListMonad From a0542fe277f720d576cfc70d197b5d195ab73e5e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 18:29:13 +0200 Subject: [PATCH 44/54] Fix rebase breakage --- .../dotty/tools/dotc/printing/RefinedPrinter.scala | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 5515149845e2..83532f865477 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -459,17 +459,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) } - case tree @ Extension(name, constr, tpt, impl) => - withEnclosingDef(tree) { - modText(tree.mods, keywordStr("extension")) ~~ - nameIdText(tree) ~ - { withEnclosingDef(constr) { - addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1)) - } - } ~ - " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ - toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) - } case SymbolLit(str) => "'" + str case InterpolatedString(id, segments) => From 46c7809f62f2d497f714e78c405ab06a3ce66ab4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 19:11:09 +0200 Subject: [PATCH 45/54] Polishings --- .../reference/extend/common-declarations.md | 4 ++-- docs/docs/reference/extend/context-bounds.md | 24 +++++++++---------- .../reference/extend/factored-instances.md | 10 ++++---- docs/docs/reference/extend/local-coherence.md | 2 +- .../extend/parameterized-typeclasses.md | 2 +- .../docs/reference/extend/typeclass-traits.md | 6 ++++- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/docs/docs/reference/extend/common-declarations.md b/docs/docs/reference/extend/common-declarations.md index 3575e9330bd9..15ae67cbc1be 100644 --- a/docs/docs/reference/extend/common-declarations.md +++ b/docs/docs/reference/extend/common-declarations.md @@ -48,7 +48,7 @@ val erroneous = Text.fromStrings("hello", ", world") // error: not found The `common` definition of `fromString` in `FlatText` implicitly defines a member of `object FlatText`. Alternatively, one could have defined it as a regular method in the companion object. This is done in the following second implementation of `Text`, which -represents a text as a tree of strings. Both old and new implementations share the definition of the `common` method `fromStrings` in `Text`. +represents a text as a tree of strings. The two implementations share the definition of the `common` method `fromStrings` in `Text`. ```scala enum ConcText { @@ -87,7 +87,7 @@ trait Text { def flatten: Instance = fromString(toStr) } ``` -Why does this work? The `fromString` method is abstract in `Text` so how to we find the correct implementation in `flatten`? +Why does this work? The `fromString` method is abstract in `Text` so how do we find the correct implementation in `flatten`? Comparing with the `toStr` reference, that one is an instance method and therefore is expanded to `this.toStr`. But the same does not work for `fromString` because it is a `common` method, not an instance method. In fact, the application above is syntactic sugar for diff --git a/docs/docs/reference/extend/context-bounds.md b/docs/docs/reference/extend/context-bounds.md index 6d2567a9763b..581b9529c934 100644 --- a/docs/docs/reference/extend/context-bounds.md +++ b/docs/docs/reference/extend/context-bounds.md @@ -34,7 +34,7 @@ assert( sum(List(Set(1, 2), Set(3))) == Set(1, 2, 3) ) assert( sum(List(S(Z), S(S(Z)))) == List(S(S(S(Z)))) ) ``` -Here is an alternative formulation of `sum` which makes the implicit evidence parameter explicit: +Here is an equivalent formulation of `sum` which makes the implicit evidence parameter explicit: ```scala def sum[T](xs: Iterable[T])(implicit ev: Monoid.Impl[T]): T = @@ -69,7 +69,7 @@ method `impl`, specified as follows: ```scala def impl[T](implicit ev: Impl[T]): Impl[T] = ev ``` -This method is used in the the first implementation of `sum` above to retrieve +This method is used in the first implementation of `sum` above to retrieve the monoid's `unit` value: ```scala def sum[T: Monoid](xs: Iterable[T]): T = @@ -89,19 +89,19 @@ The details of the conversion are as follows: 1. The `scala` package defines an `Injector` trait as follows: ```scala - trait Injector { - /** The implementing type */ - type This + trait Injector { + /** The implementing type */ + type This - /** The implemented trait */ - type $Instance + /** The implemented trait */ + type $Instance - /** The implementation via type `T` for this trait */ - implicit def inject(x: This): $Instance - } + /** The implementation via type `T` for this trait */ + implicit def inject(x: This): $Instance + } ``` - The `This` type of this injector is the same as the `This` type of an implementing typeclass trait; it representing the implementing type. The `$Instance` type is name-mangled and therefore should not be referred to in user programs. It refers to the implemented typeclass instance. An `inject` method converts from the first to the second. + The `This` type of this injector is the same as the `This` type of an implementing typeclass trait; it represents the implementing type. The `$Instance` type is name-mangled, so user programs should not refer to it. It represents the implemented typeclass instance. The `inject` method converts from the first to the second. 2. Every implementation object `TC.Impl[T]` inherits an `Injector` instance where `This = T` and `$Instance <: TC`. Hence, it defines an `inject` conversion from `T` to `TC`. @@ -134,7 +134,7 @@ If class `C` extends `B` directly, the context bound `[C : B]` is resolved to `s def inject(x: T) = x } ``` -In other words, context bounds [T: B] for non-typeclass traits `B` work similar to the previous (now deprecated) view bounds [T <% B] in that they specify an implicit conversion between `T` and `B`. +In other words, context bounds [T: B] for non-typeclass traits `B` work similar to the previous (now deprecated) view bounds `[T <% B]` in that they specify an implicit conversion from `T` to `B`. ### Conditional Instance Declarations diff --git a/docs/docs/reference/extend/factored-instances.md b/docs/docs/reference/extend/factored-instances.md index fbed41c2468a..11347d0069a4 100644 --- a/docs/docs/reference/extend/factored-instances.md +++ b/docs/docs/reference/extend/factored-instances.md @@ -32,13 +32,13 @@ trait Traverse[A] extends Applicative[A] { } ``` -The `List` type can be made an instance of both `Monad` and `Traverse` by writing an instance declaration for `List[T] : Monad[T]` and another one for `List[T] : Traverse[T]`. +The `List` type can be made an instance of both `Monad` and `Traverse` by writing an instance declaration for `List : Monad` and another one for `List : Traverse`. However, these two declarations would then need to duplicate the definitions of the `Applicative` methods `map2` and `pure`. Factored instance declarations provide a way to avoid the duplication. The idea is to write three instance declarations, for `Applicative`, `Monad`, and `Traverse`: ```scala -extension ListApplicative[A] for List[A]: Applicative[A] { +extension ListApplicative[A] for List : Applicative { def map[B](f: A => B): List[B] = this.map(f) @@ -48,12 +48,12 @@ extension ListApplicative[A] for List[A]: Applicative[A] { def pure[B]: List[B] = Nil } -extension ListMonad[A] for List[A] : Monad[A] { +extension ListMonad[A] for List : Monad { def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) } -extension ListTraverse[A] for List[A] : Traverse[A] { +extension ListTraverse[A] for List : Traverse { def traverse[B, C[_]: Applicative](f: A => C[B]): C[List[B]] = this match { case Nil => Applicative.impl[C].pure[List[B]] @@ -64,7 +64,7 @@ extension ListTraverse[A] for List[A] : Traverse[A] { In the definitions above, `ListMonad` and `ListTraverse` lack definitions for `map2` and `pure`. These definitions are provided implicitly by forwarding to corresponding definitions in the `ListApplicative` instance declaration. If we had written the forwarders for `ListMonad` explicitly, this would look like the following, alternative definition: ```scala -extension ListMonad[A] for List[A] : Monad[A] { +extension ListMonad[A] for List : Monad { def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) // The following can be implicitly inserted: diff --git a/docs/docs/reference/extend/local-coherence.md b/docs/docs/reference/extend/local-coherence.md index 5ddbfc39a80e..d1df351d79fc 100644 --- a/docs/docs/reference/extend/local-coherence.md +++ b/docs/docs/reference/extend/local-coherence.md @@ -4,7 +4,7 @@ title: "Local Coherence" --- In the context of instance declarations, global coherence means that for every type `I` and typeclass trait `TC` there is never more than one way in which `I` implements `TC`. -Scala does not have a global coherence requirement for its implicits. Instead it reports an ambiguity error if an implicit search yields several different results. The advantage of Scala's approach is its flexibility, in particular its support scoped implicits. But sometimes the ambiguity rules get in the way. Consider for instance the typeclass trait hierarchy presented in the last section, which involves `Functor`, `Applicative`, `Monad`, and `Traverse`. Now add a function like `transform` below that requires its parameter type +Scala does not have a global coherence requirement for its implicits. Instead it reports an ambiguity error if an implicit search yields several different results. The advantage of Scala's approach lies in its flexibility, in particular its support for scoped implicits. But sometimes the ambiguity rules get in the way. Consider for instance the typeclass trait hierarchy presented in the last section, which involves `Functor`, `Applicative`, `Monad`, and `Traverse`. Now add a function like `transform` below that requires its parameter type to be both a `Monad` and a `Traverse`. ```scala def transform[F[_] : Monad : Traverse](x: F[A], f: A => A) = { ... x.map(f) ... } diff --git a/docs/docs/reference/extend/parameterized-typeclasses.md b/docs/docs/reference/extend/parameterized-typeclasses.md index d0273f2316c4..700dd7438e85 100644 --- a/docs/docs/reference/extend/parameterized-typeclasses.md +++ b/docs/docs/reference/extend/parameterized-typeclasses.md @@ -56,7 +56,7 @@ extension ListMonad[A] for List : Monad { } ``` -Implementations of higher-kinded typeclass traits have to have the same type parameters as the traits they implement, but can also can introduce new type parameters. New type parameters have to follow the type parameters passed along to the typeclass traits. Examples: +Implementations of higher-kinded typeclass traits have to have the same type parameters as the traits they implement, but they can also introduce new type parameters. New type parameters have to follow the type parameters passed along to the typeclass traits. Examples: ```scala trait T[X, +Y] { diff --git a/docs/docs/reference/extend/typeclass-traits.md b/docs/docs/reference/extend/typeclass-traits.md index 91630869b400..3af1909a3d61 100644 --- a/docs/docs/reference/extend/typeclass-traits.md +++ b/docs/docs/reference/extend/typeclass-traits.md @@ -63,4 +63,8 @@ But we could not have re-implemented `add` in `B` with ``` since `This` is already fixed to be `A`. -`This` can be thought of as an abstract type member of a typeclass trait. While it is possible to constrain or bind `This` explicitly, this is not encouraged because it might conflict with the compiler-generated bindings of `This`. +`This` can be thought of as am`common` abstract type member of a typeclass trait. It is as if `This` was defined explicitly as +```scala +common type This +``` +While it is possible to constrain or bind `This` explicitly, this is not encouraged because it might conflict with the compiler-generated bindings for `This`. From 0453e0598cb511ea9a7d475072f876fd520d0932 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 1 Apr 2018 17:48:35 +0200 Subject: [PATCH 46/54] Small fixes to docs --- docs/docs/reference/dropped/existential-types.md | 2 +- docs/docs/reference/enums/desugarEnums.md | 4 ++-- docs/docs/reference/extend/local-coherence.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/docs/reference/dropped/existential-types.md b/docs/docs/reference/dropped/existential-types.md index 747a51060116..b0b05d46a899 100644 --- a/docs/docs/reference/dropped/existential-types.md +++ b/docs/docs/reference/dropped/existential-types.md @@ -29,4 +29,4 @@ of `Int`. When reading classfiles compiled with _scalac_, Dotty will do a best effort to approximate existential types with its own types. It will -issue a warning that a precise emulation is not possible. +issue a warning when a precise emulation is not possible. diff --git a/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index ac53adef5911..895e965dd97d 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -13,7 +13,7 @@ some terminology and notational conventions: - We use `E` as a name of an enum, and `C` as a name of a case that appears in `E`. - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance, - `` represents one or more a parameter lists `(...)` or nothing at all. + `` represents one or more parameter lists `(...)` or nothing at all. - Enum cases fall into three categories: @@ -64,7 +64,7 @@ map into case classes or vals. val C = $new(n, "C") - Here, `$new` is a private method that creates an instance of of `E` (see + Here, `$new` is a private method that creates an instance of `E` (see below). 4. If `E` is an enum with type parameters diff --git a/docs/docs/reference/extend/local-coherence.md b/docs/docs/reference/extend/local-coherence.md index d1df351d79fc..21308f446b9b 100644 --- a/docs/docs/reference/extend/local-coherence.md +++ b/docs/docs/reference/extend/local-coherence.md @@ -10,7 +10,7 @@ to be both a `Monad` and a `Traverse`. def transform[F[_] : Monad : Traverse](x: F[A], f: A => A) = { ... x.map(f) ... } ``` The call to `map` in the body of that function would be in error, because it is ambiguous - we don't know whether we should call the `map` method defined in `Monad` or -the one defined in `Traverse`. To see this in more detail, expand the context bounds in `transform` and add a type ascription `x`: +the one defined in `Traverse`. To see this in more detail, expand the context bounds in `transform` and add a type ascription for `x`: ```scala def transform[F[_], A](x: F[A], f: A => A)( @@ -25,10 +25,10 @@ This problem was brought up in issue #2029 and was presented in a [paper at the Scala Symposium 2017](https://adelbertc.github.io/publications/typeclasses-scala17.pdf). Note that if we assume `List` as the actual implementation, this ambiguity is a false negative, since `List`'s implementation of `map` is the same for `Monad` and `Traverse`, -being defined in the common extension `Applicative`. But in general, a type might well have two different implementations for `map` (or `Functor` methods in general) when seen as +being defined in the common extension `ListApplicative`. But in general, a type might well have two different implementations for `map` (or `Functor` methods in general) when seen as a `Monad` or as a `Traverse`. -The (as yes tentative) idea to solve the problem is to allow a way to constrain implicit values to have common implementations for some of their super-traits. +The (as yet tentative) idea to solve the problem is to provide a way to constrain implicit values to have common implementations for some of their super-traits. This can be done by introducing a predefined type constructor `Super[_]` as a member of all implementation objects of typeclass traits. If `impl` is an implementation object for type `I` and typeclass trait `T`, and `U` is a typeclass trait extended by `T`, then `impl.Super[U]` would give the type of the implementation object from which `impl` obtains all implementations of `U`. For instance, assuming the factored instance declarations in the last section, @@ -41,7 +41,7 @@ On the other hand, if we had not used factorization for `ListMonad`, `ListMonad.Super[Functor]` would be `ListMonad.type`, since it would be `ListMonad` that defines some of the methods in `Functor`. -Once we have the `Super` type defined like this, we can add typeclass constraints to enforce equalities. For instance: +Once we have the `Super` type defined like this, we can add type constraints to enforce equalities. For instance: ```scala def transform[F[_], A](x: F[A], f: A => A)( From 434a6896c3de6f9ed760cea3fed1c28b6a411a68 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 Apr 2018 16:39:36 +0200 Subject: [PATCH 47/54] Fix test --- .../dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala index bb8db2aff35c..c59e8fbb8782 100644 --- a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala +++ b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala @@ -88,6 +88,7 @@ class UserDefinedErrorMessages extends ErrorMessagesTest { checkMessagesAfter("frontend") { """ |class C { + | import language.implicitConversions | @annotation.implicitAmbiguous("msg A=${A}") | implicit def f[A](x: Int): String = "f was here" | implicit def g(x: Int): String = "f was here" From 6e188850d8b6750008d0b93f43ed9fd751c65dc9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 Apr 2018 19:22:39 +0200 Subject: [PATCH 48/54] Flesh out documentation - Adds syntax and type checking overview - Fixes some typos --- docs/docs/internals/syntax.md | 2 + .../reference/extend/instance-declarations.md | 10 -- docs/docs/reference/extend/rules.md | 98 +++++++++++++++++++ docs/docs/reference/extend/translation.md | 5 + .../docs/reference/extend/typeclass-traits.md | 2 +- 5 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 docs/docs/reference/extend/rules.md diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index c7b8e622a726..4b532957bd35 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -109,6 +109,7 @@ ids ::= id {‘,’ id} Path ::= StableId | [id ‘.’] ‘this’ + | Path ‘.’ ‘common’ StableId ::= id | Path ‘.’ id | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id @@ -281,6 +282,7 @@ Binding ::= (id | ‘_’) [‘:’ Type] Modifier ::= LocalModifier | AccessModifier | ‘override’ + | ‘common’ LocalModifier ::= ‘abstract’ | ‘final’ | ‘sealed’ diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md index 7d7fdd544b13..dcdc89e316db 100644 --- a/docs/docs/reference/extend/instance-declarations.md +++ b/docs/docs/reference/extend/instance-declarations.md @@ -60,13 +60,3 @@ the implemented trait must be an interface trait according to the following defi - does not have initialization statements, and - has only interface traits as parents. -### Syntax of Extensions - -The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) - - TmplDef ::= ... - | ‘extension’ ExtensionDef - ExtensionDef ::= id [ExtensionParams] 'for' Type ExtensionClause - ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] - ExtensionClause ::= [`:` Template] - | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ diff --git a/docs/docs/reference/extend/rules.md b/docs/docs/reference/extend/rules.md new file mode 100644 index 000000000000..9530a321fc7f --- /dev/null +++ b/docs/docs/reference/extend/rules.md @@ -0,0 +1,98 @@ +--- +layout: doc-page +title: "Syntax and Type Checking" +--- + +This section summarizes syntax changes and gives an outline of envisaged type checking rules. The type checking part is very provisional since details will probably depend on the +implemented encoding, which is not decided yet (see next section) + +## Syntax Changes + +The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + +New Keywords: + + extension common + +New Productions: + + TmplDef ::= ... + | ‘extension’ ExtensionDef + ExtensionDef ::= id [ExtensionParams] 'for' Type ExtensionClause + ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + + Modifier ::= ... + | ‘common’ + Path ::= ... + | Path ‘.’ ‘common’ + + +## Type Checking Outline + +Here's an outline of the type checking rules and the auxiliary definitions implicitly added to programs. To keep things simple, this section only talks about the first-order case. +The section on "Parameterized Typeclass Traits" talks a bit how to extend this to higher-kinds, and the[https://github.com/dotty-staging/dotty/blob/add-common/tests/pos/typeclass-encoding3.scala](typeclass encoding test case) provides further examples. + +### TypeClass Traits + +There is a new marker trait `scala.TypeClass`. Traits and classes inheriting directly or indirectly from `TypeClass` are called _typeclass traits_. They differ as follows from normal traits: + + - They can have `common` definitions + - They can refer to the `This` + +The companion object of a typeclass trait `TC` contains two compiler-generated members + + - A type `Impl[T]` mapping an implementation type `T` of the trait to the type of the + corresponding implementation object, which coincides with `T`'s `common` part. + + - A method `impl[T]` returning the implementation object of `T` for `TC`. + +### The `This` Type + +The `This` type is modeled as an abstract type. It is as if each typeclass trait +was augmented by the following definition: + + common type This + +Typeclass traits have an implicit conversion in scope that converts `This` to an +instance of the typeclass trait. + +### Rules for Common + +Common definitions cannot see instance members of their enclosing trait. Common definitions in classes (but not traits) _can_ see the type parameters of the enclosing class. + +A Typeclass trait contains an instance member `common` that refers to the object containing the `common` members of the trait. If `p` refers to a typeclass trait, then `p.common` refers to this object. + +### Typeclass Implementation + +An implementation of a typeclass trait is a class or object that extends the trait (possibly that implementation is generated from an extension clause). +An implementation `C` fixes the type of `This` to + + type This = C + +unless `C` extends another implementation class `B` (in this case `B` has already defined `This`.) + +### Injections + +There is a new standard type `scala.Injectable[T, U]` that allows to abstract over implicit conversions similar to how `scala.<:<` abstracts over subtyping. The `Impl` types +in typeclass traits inherit this type. An extension clause +```scala +extension X for C : T { ... } +``` +creates an instance of `Injectable[C, T]` (which is also an `Impl` in the case where `T` is a typeclass trait). There is a standard summon of `Injectable[T, U]` if `T` is a subtype of `U`. + +Finally, there is a global implicit conversion that uses `Injectable`: + +```scala + implicit def $inject[From, To](x: From)(implicit ev: Injectable[From, To]: To +``` + +### Context Bounds + +A context bound `[T: B]` expands to an implicit evidence parameter of one of the following types: + + - If `B` has one parameter more than `T`: `B[T]` (this is the previous meaning of context bounds) + - Otherwise, if `B` is a typeclass trait: `B.Impl[T]` + - Otherwise, if `B` is a standard trait: `Injectable[T, B]` + diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md index 35b29601c8e4..5abe5d13714a 100644 --- a/docs/docs/reference/extend/translation.md +++ b/docs/docs/reference/extend/translation.md @@ -8,6 +8,11 @@ extension methods and instance declarations, it would be good to present a trans possibilities for such a translation. We should to experiment further to clarify which scheme to prefer and flesh it out in detail. +Here are links to two possible encodings: + + - A test in this PR: https://github.com/dotty-staging/dotty/blob/add-common/tests/pos/typeclass-encoding3.scala + - An alternative encoding: https://gist.github.com/OlivierBlanvillain/d314ddbcb640e2ce5604d860b5073008. + --- Extensions are closely related to implicit classes and can be translated into them. In short, diff --git a/docs/docs/reference/extend/typeclass-traits.md b/docs/docs/reference/extend/typeclass-traits.md index 3af1909a3d61..1b54ffc6011e 100644 --- a/docs/docs/reference/extend/typeclass-traits.md +++ b/docs/docs/reference/extend/typeclass-traits.md @@ -8,7 +8,7 @@ Typeclass traits offer two new ways to express the type structure of implementin A typeclass trait is a trait that extends directly or indirectly the marker trait `scala.TypeClass`. Here are two typeclass traits: ```scala trait SemiGroup extends TypeClass { - def add(that: This) + def add(that: This): This } trait Monoid extends SemiGroup { common def unit: This From 26ce98a300cbda57037a7cd10dfbb7cbb5c87a4e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 2 Apr 2018 19:30:45 +0200 Subject: [PATCH 49/54] Fix more typos --- docs/docs/reference/extend/rules.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/reference/extend/rules.md b/docs/docs/reference/extend/rules.md index 9530a321fc7f..54198c064a4f 100644 --- a/docs/docs/reference/extend/rules.md +++ b/docs/docs/reference/extend/rules.md @@ -32,14 +32,14 @@ New Productions: ## Type Checking Outline Here's an outline of the type checking rules and the auxiliary definitions implicitly added to programs. To keep things simple, this section only talks about the first-order case. -The section on "Parameterized Typeclass Traits" talks a bit how to extend this to higher-kinds, and the[https://github.com/dotty-staging/dotty/blob/add-common/tests/pos/typeclass-encoding3.scala](typeclass encoding test case) provides further examples. +The section on "Parameterized Typeclass Traits" talks a bit how to extend this to higher-kinds, and the[typeclass encoding test case](https://github.com/dotty-staging/dotty/blob/add-common/tests/pos/typeclass-encoding3.scala) provides further examples. ### TypeClass Traits There is a new marker trait `scala.TypeClass`. Traits and classes inheriting directly or indirectly from `TypeClass` are called _typeclass traits_. They differ as follows from normal traits: - They can have `common` definitions - - They can refer to the `This` + - They can refer to the type `This` The companion object of a typeclass trait `TC` contains two compiler-generated members @@ -51,7 +51,7 @@ The companion object of a typeclass trait `TC` contains two compiler-generated m ### The `This` Type The `This` type is modeled as an abstract type. It is as if each typeclass trait -was augmented by the following definition: +was augmented with the following definition: common type This From 4b1ba83ca5bc4dd9d5f8a57af091661c4043f119 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 3 Apr 2018 16:17:50 +0200 Subject: [PATCH 50/54] Merge vendethiel/patch-1 I am submitting myself to avoid the CLA problem. --- docs/docs/reference/extend/translation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md index 5abe5d13714a..67cb8ff7cd4a 100644 --- a/docs/docs/reference/extend/translation.md +++ b/docs/docs/reference/extend/translation.md @@ -66,7 +66,7 @@ implicit class SeqOps[T](private val $this: List[T]) extends AnyVal { Now, assume an extension - extension if for : { } + extension for : { } where ``, `` and `` are as before. This extension is translated to From 0cfc375581cf1974b88f38572dad2ed9665a9ad0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 4 Apr 2018 11:02:54 +0200 Subject: [PATCH 51/54] Name-mangle both injector parameters --- tests/pos/typeclass-encoding3.scala | 45 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala index f01ffde0cc21..b0a71e0f3b1a 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos/typeclass-encoding3.scala @@ -18,26 +18,26 @@ object runtime { trait Injector { /** The implementating type */ - type This + type $This /** The implemented trait */ type $Instance /** The implementation via type `T` for this trait */ - implicit def inject(x: This): $Instance + implicit def inject(x: $This): $Instance } trait IdentityInjector extends Injector { - def inject(x: This): $Instance = x.asInstanceOf + def inject(x: $This): $Instance = x.asInstanceOf } trait SubtypeInjector[T] extends Injector { - type This = T + type $This = T type $Instance = T def inject(x: T): T = x } - type Injectable[T, +U] = Injector { type This = T; type $Instance <: U } + type Injectable[T, +U] = Injector { type $This = T; type $Instance <: U } def selfInject[U, T <: U]: Injectable[T, U] = new SubtypeInjector[T] {} @@ -50,6 +50,7 @@ object runtime { /** Base trait for companion objects of all implementations of this typeclass */ trait Common extends Injector { self => + type This = $This /** The implemented typeclass */ type $Instance <: TypeClass } @@ -60,14 +61,14 @@ object runtime { type Common <: TypeClass.Common /** Helper type to characterize implementations via type `T` for this typeclass */ - type Impl[T] = Common { type This = T } + type Impl[T] = Common { type $This = T } /** The implementation via type `T` for this typeclass, as found by implicit search */ def impl[T](implicit ev: Impl[T]): Impl[T] = ev } } - implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type This = From }): ev.$Instance = + implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type $This = From }): ev.$Instance = ev.inject(x) } @@ -91,7 +92,7 @@ object runtime { common def limit: Int } - trait HasBoundedLengthX extends HasBoundedLength { + trait HasBoundedLengthX extends HasBoundedLength with TypeClass { common def longest: This } @@ -324,7 +325,7 @@ object hasLength { import `common`._ } abstract class C3Common extends C2Common with HasBoundedLengthX.Common { self => - type This = C3 + type $This = C3 type $Instance <: C3 { val `common`: self.type } def longest = new C3(new Array[Int](limit)) @@ -336,7 +337,7 @@ object hasLength { import `common`._ } abstract class CG3Common[T](implicit tag: ClassTag[T]) extends CG2Common[T] with HasBoundedLengthX.Common { self => - type This = CG3[T] + type $This = CG3[T] type $Instance <: CG3[T] { val `common`: self.type } def longest = new CG3(new Array[T](limit)) } @@ -354,7 +355,7 @@ object hasLength { class DG3[T](val xs: Array[T]) implicit object DHasLength extends Injector { - type This = D1 + type $This = D1 type $Instance = HasLength def inject(x: D1) = new HasLength { def length = xs.length @@ -362,7 +363,7 @@ object hasLength { } class DGHasLength[T] extends Injector { - type This = DG1[T] + type $This = DG1[T] type $Instance = HasLength def inject(x: DG1[T]) = new HasLength { def length = xs.length @@ -371,7 +372,7 @@ object hasLength { implicit def DGHasLength[T]: DGHasLength[T] = new DGHasLength object DHasBoundedLength extends HasBoundedLength.Common { self => - type This = D2 + type $This = D2 type $Instance = HasBoundedLength def inject(x: D2) = new HasBoundedLength { val `common`: self.type = self @@ -382,7 +383,7 @@ object hasLength { } class DGHasBoundedLength[T] extends HasBoundedLength.Common { self => - type This = DG2[T] + type $This = DG2[T] type $Instance = HasBoundedLength def inject(x: DG2[T]) = new HasBoundedLength { val `common`: self.type = self @@ -394,7 +395,7 @@ object hasLength { implicit def DGHasBoundedLength[T]: DGHasBoundedLength[T] = new DGHasBoundedLength implicit object DHasBoundedLengthX extends HasBoundedLengthX.Common { self => - type This = D3 + type $This = D3 type $Instance = HasBoundedLengthX { val `common`: self.type } def inject(x: D3) = new HasBoundedLengthX { val `common`: self.type = self @@ -406,7 +407,7 @@ object hasLength { } class DGHasBoundedLengthX[T](implicit tag: ClassTag[T]) extends HasBoundedLengthX.Common { self => - type This = DG3[T] + type $This = DG3[T] type $Instance = HasBoundedLengthX { val `common`: self.type } def inject(x: DG3[T]) = new HasBoundedLengthX { val `common`: self.type = self @@ -570,7 +571,7 @@ object semiGroups { } implicit object IntSemiGroup extends SemiGroup.Common { self => - type This = Int + type $This = Int type $Instance = SemiGroup { val `common`: self.type } def inject($this: Int) = new SemiGroup { val `common`: self.type = self @@ -579,7 +580,7 @@ object semiGroups { } implicit object IntMonoid extends Monoid.Common { self => - type This = Int + type $This = Int type $Instance = Monoid { val `common`: self.type } def unit: Int = 0 def inject($this: Int) = new Monoid { @@ -589,7 +590,7 @@ object semiGroups { } implicit object StringOps extends Monoid.Common { - type This = String + type $This = String type $Instance = Monoid { val `common`: StringOps.type } def unit = "" def inject($this: String) = new Monoid { @@ -610,7 +611,7 @@ object semiGroups { val `common`: Nat.type = Nat } object Nat extends Monoid.Common { - type This = Nat + type $This = Nat type $Instance = Nat def unit = Nat.Z def inject($this: Nat) = $this @@ -678,7 +679,7 @@ object ord { } implicit object IntOrd extends Ord.Common { - type This = Int + type $This = Int type $Instance = Ord { val `common`: IntOrd.type } val minimum: Int = Int.MinValue def inject($this: Int) = new Ord { @@ -690,7 +691,7 @@ object ord { } class ListOrd[T](implicit $ev: Ord.Impl[T]) extends Ord.Common { self => - type This = List[T] + type $This = List[T] type $Instance = Ord { val `common`: self.type } def minimum: List[T] = Nil def inject($this: List[T]) = new Ord { From 07256c2dc0f92c8fbd3bd6b174cc54b12e04d95a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Apr 2018 13:44:26 +0200 Subject: [PATCH 52/54] Disentangle common and typeclass Needs kind polymorphism to work. Seems the is the first time we kind polymorphism it in the wild! --- .../dotty/tools/dotc/CompilationTests.scala | 1 + .../typeclass-encoding3.scala | 111 ++++++++++-------- 2 files changed, 61 insertions(+), 51 deletions(-) rename tests/{pos => pos-special}/typeclass-encoding3.scala (87%) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 2a7b78b6c64c..a1bffc4e640a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -60,6 +60,7 @@ class CompilationTests extends ParallelTesting { compileFile("tests/pos-special/i3589-b.scala", defaultOptions.and("-Xfatal-warnings")) + compileFile("tests/pos-special/i4166.scala", defaultOptions.and("-Xfatal-warnings")) + compileFile("tests/pos-special/i4185.scala", defaultOptions.and("-Xfatal-warnings")) + + compileFile("tests/pos-special/typeclass-encoding3.scala", defaultOptions.and("-Ykind-polymorphism")) + compileFile("tests/pos-special/completeFromSource/Test.scala", defaultOptions.and("-sourcepath", "tests/pos-special")) + compileFile("tests/pos-special/completeFromSource/Test2.scala", defaultOptions.and("-sourcepath", "tests/pos-special")) + compileFile("tests/pos-special/completeFromSource/Test3.scala", defaultOptions.and("-sourcepath", "tests/pos-special", "-scansource")) + diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos-special/typeclass-encoding3.scala similarity index 87% rename from tests/pos/typeclass-encoding3.scala rename to tests/pos-special/typeclass-encoding3.scala index b0a71e0f3b1a..1059dadc6c0c 100644 --- a/tests/pos/typeclass-encoding3.scala +++ b/tests/pos-special/typeclass-encoding3.scala @@ -1,9 +1,9 @@ /* - [T : M] = [T] ... (implicit ev: Injectable[T, M]) if M is a normal trait - = [T] ... (implicit ev: M.Impl[T]) if M is a trait with common members + [T : M] = [T] ... (implicit ev: Injectable[T, M]) if M is a normal trait + = [T] ... (implicit ev: M.common[T] & Injectable[T, M]) if M is a trait with common members + = [T] ... (implicit ev: M.common[T]) if M is a typeclass (because they subsume Injectable) - M.Impl[T] = Common { type This = T } - M.Impl[T[_]] = Common { type This = T } + M.common[T] = Common { type $This = T } ... - Common definitions in traits may not see trait parameters, but common definitions @@ -24,7 +24,7 @@ object runtime { type $Instance /** The implementation via type `T` for this trait */ - implicit def inject(x: $This): $Instance + def inject(x: $This): $Instance } trait IdentityInjector extends Injector { @@ -41,6 +41,21 @@ object runtime { def selfInject[U, T <: U]: Injectable[T, U] = new SubtypeInjector[T] {} + trait Common { + type $This <: AnyKind + } + + trait Companion { + /** The `Common` base trait defining common (static) operations of this typeclass */ + type Common <: runtime.Common + + /** Helper type to characterize implementations via type `T` for this typeclass */ + type common[T <: AnyKind] = Common { type $This = T } + + /** The implementation via type `T` for this typeclass, as found by implicit search */ + def common[T <: AnyKind](implicit ev: common[T]): common[T] = ev + } + trait TypeClass { /** The companion object of the implementing type */ val `common`: TypeClass.Common @@ -49,22 +64,20 @@ object runtime { object TypeClass { /** Base trait for companion objects of all implementations of this typeclass */ - trait Common extends Injector { self => + trait Common extends runtime.Common with Injector { self => + /** A user-accessible self type */ type This = $This + /** The implemented typeclass */ type $Instance <: TypeClass + + implicit def inject(x: $This): $Instance } /** Base trait for the companion objects of type classes themselves */ - trait Companion { + trait Companion extends runtime.Companion { /** The `Common` base trait defining common (static) operations of this typeclass */ type Common <: TypeClass.Common - - /** Helper type to characterize implementations via type `T` for this typeclass */ - type Impl[T] = Common { type $This = T } - - /** The implementation via type `T` for this typeclass, as found by implicit search */ - def impl[T](implicit ev: Impl[T]): Impl[T] = ev } } @@ -169,10 +182,10 @@ object runtime { x.length < x.common.limit def lengthOKX[T : HasBoundedLengthX](x: T) = - x.length < HasBoundedLengthX.impl[T].limit + x.length < HasBoundedLengthX.common[T].limit def longestLengthOK[T : HasBoundedLengthX](implicit tag: ClassTag[T]) = { - val impl = HasBoundedLengthX.impl[T] + val impl = HasBoundedLengthX.common[T] impl.longest.length < impl.limit } @@ -249,28 +262,25 @@ object hasLength { def length: Int } - trait Cmp[A] extends TypeClass { - val `common`: HasBoundedLength.Common + trait Cmp[A] { + val `common`: Cmp.Common import `common`._ def isSimilar(x: A): Boolean } - object Cmp extends TypeClass.Companion { - trait Common extends TypeClass.Common { - type $Instance <: HasBoundedLength + object Cmp extends Companion { + trait Common extends runtime.Common { def exact: Boolean } } - - trait HasBoundedLength extends HasLength with TypeClass { + trait HasBoundedLength extends HasLength { val `common`: HasBoundedLength.Common import `common`._ } - object HasBoundedLength extends TypeClass.Companion { - trait Common extends TypeClass.Common { - type $Instance <: HasBoundedLength + object HasBoundedLength extends Companion { + trait Common extends runtime.Common { def limit: Int } } @@ -371,7 +381,7 @@ object hasLength { } implicit def DGHasLength[T]: DGHasLength[T] = new DGHasLength - object DHasBoundedLength extends HasBoundedLength.Common { self => + object DHasBoundedLength extends HasBoundedLength.Common with Injector { self => type $This = D2 type $Instance = HasBoundedLength def inject(x: D2) = new HasBoundedLength { @@ -382,7 +392,7 @@ object hasLength { def limit = 100 } - class DGHasBoundedLength[T] extends HasBoundedLength.Common { self => + class DGHasBoundedLength[T] extends HasBoundedLength.Common with Injector { self => type $This = DG2[T] type $Instance = HasBoundedLength def inject(x: DG2[T]) = new HasBoundedLength { @@ -424,11 +434,11 @@ object hasLength { def lengthOK[T](x: T)(implicit ev: Injectable[T, HasBoundedLength]) = x.length < x.common.limit - def lengthOKX[T](x: T)(implicit ev: HasBoundedLength.Impl[T]) = - x.length < HasBoundedLength.impl[T].limit + def lengthOKX[T](x: T)(implicit ev: HasBoundedLength.common[T] & Injectable[T, HasBoundedLength]) = + x.length < HasBoundedLength.common[T].limit - def longestLengthOK[T](implicit ev: HasBoundedLengthX.Impl[T], tag: ClassTag[T]) = { - val impl = HasBoundedLengthX.impl[T] + def longestLengthOK[T](implicit ev: HasBoundedLengthX.common[T], tag: ClassTag[T]) = { + val impl = HasBoundedLengthX.common[T] impl.longest.length < impl.limit } @@ -539,7 +549,7 @@ object hasLength { } def sum[T: Monoid](xs: List[T]): T = - (Monoid.impl[T].unit /: xs)(_ `add` _) + (Monoid.common[T].unit /: xs)(_ `add` _) */ import runtime._ @@ -620,8 +630,8 @@ object semiGroups { implicit def NatOps: Nat.type = Nat - def sum[T](xs: List[T])(implicit $ev: Monoid.Impl[T]) = - (Monoid.impl[T].unit /: xs)((x, y) => x `add` y) + def sum[T](xs: List[T])(implicit $ev: Monoid.common[T]) = + (Monoid.common[T].unit /: xs)((x, y) => x `add` y) sum(List(1, 2, 3)) sum(List("hello ", "world!")) @@ -660,7 +670,7 @@ object semiGroups { def min[T: Ord](x: T, y: T) = if (x < y) x else y - def inf[T: Ord](xs: List[T]): T = (Ord.impl[T].minimum /: xs)(min) + def inf[T: Ord](xs: List[T]): T = (Ord.common[T].minimum /: xs)(min) */ object ord { @@ -690,7 +700,7 @@ object ord { } } - class ListOrd[T](implicit $ev: Ord.Impl[T]) extends Ord.Common { self => + class ListOrd[T](implicit $ev: Ord.common[T]) extends Ord.Common { self => type $This = List[T] type $Instance = Ord { val `common`: self.type } def minimum: List[T] = Nil @@ -707,14 +717,14 @@ object ord { } } } - implicit def ListOrd[T](implicit $ev: Ord.Impl[T]): ListOrd[T] = + implicit def ListOrd[T](implicit $ev: Ord.common[T]): ListOrd[T] = new ListOrd[T] - def min[T](x: T, y: T)(implicit $ev: Ord.Impl[T]): T = + def min[T](x: T, y: T)(implicit $ev: Ord.common[T]): T = if (x < y) x else y - def inf[T](xs: List[T])(implicit $ev: Ord.Impl[T]): T = { - val smallest = Ord.impl[T].minimum + def inf[T](xs: List[T])(implicit $ev: Ord.common[T]): T = { + val smallest = Ord.common[T].minimum (smallest /: xs)(min) } @@ -733,7 +743,7 @@ object ord { // Generically, `pure[A]{.map(f)}^n` def develop[A, F[_] : Functor](n: Int, f: A => A): F[A] = - if (n == 0) Functor.impl[F].pure[A] + if (n == 0) Functor.common[F].pure[A] else develop[A, F](n - 1, f).map(f) trait Monad[A] extends Functor[A] { @@ -759,16 +769,15 @@ object runtime1 { val `common`: TypeClass1.Common } object TypeClass1 { - trait Common { self => - type This[X] + trait Common extends runtime.Common { + type $This[X] + type This = $This type $Instance[X] <: TypeClass1[X] - def inject[A](x: This[A]): $Instance[A] + implicit def inject[A](x: This[A]): $Instance[A] } - trait Companion { + trait Companion extends runtime.Companion { type Common <: TypeClass1.Common - type Impl[T[_]] = Common { type This = T } - def impl[T[_]](implicit ev: Impl[T]): Impl[T] = ev } } @@ -804,12 +813,12 @@ object functors { } } - def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.Impl[F]): F[A] = - if (n == 0) Functor.impl[F].pure(x) + def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.common[F]): F[A] = + if (n == 0) Functor.common[F].pure(x) else develop(n - 1, x, f).map(f) implicit object ListMonad extends Monad.Common { - type This = List + type $This = List type $Instance[X] = Monad[X] { val `common`: ListMonad.type } def pure[A](x: A) = x :: Nil def inject[A]($this: List[A]) = new Monad[A] { @@ -820,7 +829,7 @@ object functors { } object MonadFlatten { - def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.Impl[T]): T[A] = + def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.common[T]): T[A] = $this.flatMap(identity ) } From 97f7590fdea42d0b0f9325b8719d09a2dd95504c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Apr 2018 16:35:42 +0200 Subject: [PATCH 53/54] Make the encoding of `common` more regular. --- tests/pos-special/typeclass-encoding3.scala | 132 +++++++++++++------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/tests/pos-special/typeclass-encoding3.scala b/tests/pos-special/typeclass-encoding3.scala index 1059dadc6c0c..90919879e09c 100644 --- a/tests/pos-special/typeclass-encoding3.scala +++ b/tests/pos-special/typeclass-encoding3.scala @@ -3,6 +3,12 @@ = [T] ... (implicit ev: M.common[T] & Injectable[T, M]) if M is a trait with common members = [T] ... (implicit ev: M.common[T]) if M is a typeclass (because they subsume Injectable) + The context bound [C, M] resolves to: + + - selfInject if C is a class extending a normal trait M + - C.common, C[Ts] if C is a class extending a trait M with common members + - EX if EX is an implicit extension object for C and M + M.common[T] = Common { type $This = T } ... @@ -309,50 +315,63 @@ object hasLength { } class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength with Cmp[Seq[Int]] { - val `common`: C2Common = C2 + val `common`: C2.Common = C2.common import `common`._ } - abstract class C2Common extends HasBoundedLength.Common with Cmp.Common{ - def limit = 100 - def exact = true + + object C2 { + abstract class Common extends HasBoundedLength.Common with Cmp.Common { + def limit = 100 + def exact = true + } + val common = new Common with SubtypeInjector[C2] + def limit = common.limit + def exact = common.exact } - object C2 extends C2Common with SubtypeInjector[C2] class CG2[T](xs: Array[T]) extends CG1(xs) with HasBoundedLength with Cmp[Seq[T]] { - val `common`: CG2Common[T] = CG2[T] + val `common`: CG2.Common[T] = CG2[T] import `common`._ } - abstract class CG2Common[T] extends HasBoundedLength.Common with Cmp.Common { - def limit = 100 - def exact = true - } + object CG2 { - def apply[T] = new CG2Common[T] with SubtypeInjector[CG2[T]] + abstract class Common[T] extends HasBoundedLength.Common with Cmp.Common { + def limit = 100 + def exact = true + } + def apply[T] = new Common[T] with SubtypeInjector[CG2[T]] } class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { - override val `common`: C3Common = C3 + override val `common`: C3.Common = C3.common import `common`._ } - abstract class C3Common extends C2Common with HasBoundedLengthX.Common { self => - type $This = C3 - type $Instance <: C3 { val `common`: self.type } - def longest = new C3(new Array[Int](limit)) + object C3 { + class Common extends C2.Common with HasBoundedLengthX.Common with IdentityInjector { self => + type $This = C3 + type $Instance <: C3 { val `common`: self.type } + def longest = new C3(new Array[Int](limit)) + } + val common = new Common + def limit = common.limit + def exact = common.exact + def longest = common.longest } - object C3 extends C3Common with IdentityInjector class CG3[T](xs: Array[T])(implicit tag: ClassTag[T]) extends CG2(xs) with HasBoundedLengthX { - override val `common`: CG3Common[T] = CG3[T] + override val `common`: CG3.Common[T] = CG3[T] import `common`._ } - abstract class CG3Common[T](implicit tag: ClassTag[T]) extends CG2Common[T] with HasBoundedLengthX.Common { self => - type $This = CG3[T] - type $Instance <: CG3[T] { val `common`: self.type } - def longest = new CG3(new Array[T](limit)) - } + object CG3 { - def apply[T](implicit tag: ClassTag[T]) = new CG3Common[T] with IdentityInjector + class Common[T](implicit tag: ClassTag[T]) + extends CG2.Common[T] with HasBoundedLengthX.Common with IdentityInjector { self => + type $This = CG3[T] + type $Instance <: CG3[T] { val `common`: self.type } + def longest = new CG3(new Array[T](limit)) + } + def apply[T](implicit tag: ClassTag[T]) = new Common[T] } class D1(val xs: Array[Int]) @@ -381,7 +400,7 @@ object hasLength { } implicit def DGHasLength[T]: DGHasLength[T] = new DGHasLength - object DHasBoundedLength extends HasBoundedLength.Common with Injector { self => + implicit object DHasBoundedLength extends HasBoundedLength.Common with Injector { self => type $This = D2 type $Instance = HasBoundedLength def inject(x: D2) = new HasBoundedLength { @@ -410,7 +429,7 @@ object hasLength { def inject(x: D3) = new HasBoundedLengthX { val `common`: self.type = self import `common`._ - def length = xs.length + def length = x.length } def limit = 100 def longest = new D3(new Array[Int](limit)) @@ -466,9 +485,9 @@ object hasLength { length(c1)(selfInject) length(cg1)(selfInject) - length(c2)(C2) + length(c2)(C2.common) length(cg2)(CG2[Int]) - length(c3)(C3) + length(c3)(C3.common) length(cg3)(CG3[Int]) length(d1)(DHasLength) @@ -478,9 +497,16 @@ object hasLength { length(d3)(DHasBoundedLengthX) length(dg3)(DGHasBoundedLengthX[Int]) - lengthOK(c2)(C2) + length(d1) + length(dg1) + length(d2) + length(dg2) + length(d3) + length(dg3) + + lengthOK(c2)(C2.common) lengthOK(cg2)(CG2[Int]) - lengthOK(c3)(C3) + lengthOK(c3)(C3.common) lengthOK(cg3)(CG3[Int]) lengthOK(d2)(DHasBoundedLength) @@ -488,13 +514,21 @@ object hasLength { lengthOK(d3)(DHasBoundedLengthX) lengthOK(dg3)(DGHasBoundedLengthX[Int]) - lengthOKX(c3)(C3) + lengthOK(d2) + lengthOK(dg2) + lengthOK(d3) + lengthOK(dg3) + + lengthOKX(c3)(C3.common) lengthOKX(cg3)(CG3[Int]) lengthOKX(d3)(DHasBoundedLengthX) lengthOKX(dg3)(DGHasBoundedLengthX[Int]) - longestLengthOK[C3](C3, ctag[C3]) + lengthOKX(d3) + lengthOKX(dg3) + + longestLengthOK[C3](C3.common, ctag[C3]) longestLengthOK[CG3[Int]](CG3[Int], ctag[CG3[Int]]) longestLengthOK[D3] longestLengthOK[DG3[Int]] @@ -583,7 +617,7 @@ object semiGroups { implicit object IntSemiGroup extends SemiGroup.Common { self => type $This = Int type $Instance = SemiGroup { val `common`: self.type } - def inject($this: Int) = new SemiGroup { + implicit def inject($this: Int) = new SemiGroup { val `common`: self.type = self def add(that: Int): Int = $this + that } @@ -593,18 +627,18 @@ object semiGroups { type $This = Int type $Instance = Monoid { val `common`: self.type } def unit: Int = 0 - def inject($this: Int) = new Monoid { + implicit def inject($this: Int) = new Monoid { val `common`: self.type = self - def add(that: This): This = IntSemiGroup.inject($this).add(that) + def add(that: This): This = $this.add(that) } } - implicit object StringOps extends Monoid.Common { + implicit object StringMonoid extends Monoid.Common { self => type $This = String - type $Instance = Monoid { val `common`: StringOps.type } + type $Instance = Monoid { val `common`: self.type } def unit = "" def inject($this: String) = new Monoid { - val `common`: StringOps.this.type = StringOps.this + val `common`: self.type = self def add(that: This): This = $this.concat(that) } } @@ -613,29 +647,31 @@ object semiGroups { case Z case S(n: Nat) + val `common`: Nat.Common = Nat.common + import `common`._ + def add(that: Nat): Nat = this match { case Z => that case S(n) => S(n.add(that)) } - - val `common`: Nat.type = Nat } - object Nat extends Monoid.Common { - type $This = Nat - type $Instance = Nat - def unit = Nat.Z - def inject($this: Nat) = $this + object Nat { + class Common extends Monoid.Common with IdentityInjector { self => + type $This = Nat + type $Instance <: Nat { val `common`: self.type } + def unit = Nat.Z + } + val common = new Common + def unit = common.unit } import Nat.{Z, S} - implicit def NatOps: Nat.type = Nat - def sum[T](xs: List[T])(implicit $ev: Monoid.common[T]) = (Monoid.common[T].unit /: xs)((x, y) => x `add` y) sum(List(1, 2, 3)) sum(List("hello ", "world!")) - sum(List(Z, S(Z), S(S(Z)))) + sum(List(Z, S(Z), S(S(Z))))(Nat.common) } /** 2. Generic implementations of simple type classes. From 37f842483d7a0c7c43d01359638d4fdbcf27b2db Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Apr 2018 17:00:15 +0200 Subject: [PATCH 54/54] Rename `common` from a trait companion to `at`. So, it's now `Monad.at[T]` instead of `Monad.impl[T]`, `Monad.common[T]`, or `Monad[T]`. --- tests/pos-special/typeclass-encoding3.scala | 48 ++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/pos-special/typeclass-encoding3.scala b/tests/pos-special/typeclass-encoding3.scala index 90919879e09c..9e4811756f58 100644 --- a/tests/pos-special/typeclass-encoding3.scala +++ b/tests/pos-special/typeclass-encoding3.scala @@ -1,7 +1,7 @@ /* [T : M] = [T] ... (implicit ev: Injectable[T, M]) if M is a normal trait - = [T] ... (implicit ev: M.common[T] & Injectable[T, M]) if M is a trait with common members - = [T] ... (implicit ev: M.common[T]) if M is a typeclass (because they subsume Injectable) + = [T] ... (implicit ev: M.at[T] & Injectable[T, M]) if M is a trait with common members + = [T] ... (implicit ev: M.at[T]) if M is a typeclass (because they subsume Injectable) The context bound [C, M] resolves to: @@ -9,7 +9,7 @@ - C.common, C[Ts] if C is a class extending a trait M with common members - EX if EX is an implicit extension object for C and M - M.common[T] = Common { type $This = T } + M.at[T] = Common { type $This = T } ... - Common definitions in traits may not see trait parameters, but common definitions @@ -56,10 +56,10 @@ object runtime { type Common <: runtime.Common /** Helper type to characterize implementations via type `T` for this typeclass */ - type common[T <: AnyKind] = Common { type $This = T } + type at[T <: AnyKind] = Common { type $This = T } /** The implementation via type `T` for this typeclass, as found by implicit search */ - def common[T <: AnyKind](implicit ev: common[T]): common[T] = ev + def at[T <: AnyKind](implicit ev: at[T]): at[T] = ev } trait TypeClass { @@ -188,10 +188,10 @@ object runtime { x.length < x.common.limit def lengthOKX[T : HasBoundedLengthX](x: T) = - x.length < HasBoundedLengthX.common[T].limit + x.length < HasBoundedLengthX.at[T].limit def longestLengthOK[T : HasBoundedLengthX](implicit tag: ClassTag[T]) = { - val impl = HasBoundedLengthX.common[T] + val impl = HasBoundedLengthX.at[T] impl.longest.length < impl.limit } @@ -453,11 +453,11 @@ object hasLength { def lengthOK[T](x: T)(implicit ev: Injectable[T, HasBoundedLength]) = x.length < x.common.limit - def lengthOKX[T](x: T)(implicit ev: HasBoundedLength.common[T] & Injectable[T, HasBoundedLength]) = - x.length < HasBoundedLength.common[T].limit + def lengthOKX[T](x: T)(implicit ev: HasBoundedLength.at[T] & Injectable[T, HasBoundedLength]) = + x.length < HasBoundedLength.at[T].limit - def longestLengthOK[T](implicit ev: HasBoundedLengthX.common[T], tag: ClassTag[T]) = { - val impl = HasBoundedLengthX.common[T] + def longestLengthOK[T](implicit ev: HasBoundedLengthX.at[T], tag: ClassTag[T]) = { + val impl = HasBoundedLengthX.at[T] impl.longest.length < impl.limit } @@ -583,7 +583,7 @@ object hasLength { } def sum[T: Monoid](xs: List[T]): T = - (Monoid.common[T].unit /: xs)(_ `add` _) + (Monoid.at[T].unit /: xs)(_ `add` _) */ import runtime._ @@ -666,8 +666,8 @@ object semiGroups { } import Nat.{Z, S} - def sum[T](xs: List[T])(implicit $ev: Monoid.common[T]) = - (Monoid.common[T].unit /: xs)((x, y) => x `add` y) + def sum[T](xs: List[T])(implicit $ev: Monoid.at[T]) = + (Monoid.at[T].unit /: xs)((x, y) => x `add` y) sum(List(1, 2, 3)) sum(List("hello ", "world!")) @@ -706,7 +706,7 @@ object semiGroups { def min[T: Ord](x: T, y: T) = if (x < y) x else y - def inf[T: Ord](xs: List[T]): T = (Ord.common[T].minimum /: xs)(min) + def inf[T: Ord](xs: List[T]): T = (Ord.at[T].minimum /: xs)(min) */ object ord { @@ -736,7 +736,7 @@ object ord { } } - class ListOrd[T](implicit $ev: Ord.common[T]) extends Ord.Common { self => + class ListOrd[T](implicit $ev: Ord.at[T]) extends Ord.Common { self => type $This = List[T] type $Instance = Ord { val `common`: self.type } def minimum: List[T] = Nil @@ -753,14 +753,14 @@ object ord { } } } - implicit def ListOrd[T](implicit $ev: Ord.common[T]): ListOrd[T] = + implicit def ListOrd[T](implicit $ev: Ord.at[T]): ListOrd[T] = new ListOrd[T] - def min[T](x: T, y: T)(implicit $ev: Ord.common[T]): T = + def min[T](x: T, y: T)(implicit $ev: Ord.at[T]): T = if (x < y) x else y - def inf[T](xs: List[T])(implicit $ev: Ord.common[T]): T = { - val smallest = Ord.common[T].minimum + def inf[T](xs: List[T])(implicit $ev: Ord.at[T]): T = { + val smallest = Ord.at[T].minimum (smallest /: xs)(min) } @@ -779,7 +779,7 @@ object ord { // Generically, `pure[A]{.map(f)}^n` def develop[A, F[_] : Functor](n: Int, f: A => A): F[A] = - if (n == 0) Functor.common[F].pure[A] + if (n == 0) Functor.at[F].pure[A] else develop[A, F](n - 1, f).map(f) trait Monad[A] extends Functor[A] { @@ -849,8 +849,8 @@ object functors { } } - def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.common[F]): F[A] = - if (n == 0) Functor.common[F].pure(x) + def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.at[F]): F[A] = + if (n == 0) Functor.at[F].pure(x) else develop(n - 1, x, f).map(f) implicit object ListMonad extends Monad.Common { @@ -865,7 +865,7 @@ object functors { } object MonadFlatten { - def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.common[T]): T[A] = + def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.at[T]): T[A] = $this.flatMap(identity ) }