From ab9aca14e780b687498efe8d4b449772fde6994e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 14:38:39 +0100 Subject: [PATCH 01/50] 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 252b301c2954..dcfeba5842cd 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 04c4c2e9d4bc..fef25c11fbc4 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 @@ -299,6 +300,7 @@ object TastyFormat { final val DEFAULTparameterized = 30 final val STABLE = 31 final val MACRO = 32 + final val OPAQUE = 33 // Cat. 2: tag Nat @@ -410,7 +412,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= MACRO || + firstSimpleTreeTag <= tag && tag <= OPAQUE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -432,6 +434,7 @@ object TastyFormat { | OVERRIDE | INLINE | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -486,6 +489,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 f9652f19a4d8..634100e5fe46 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -606,6 +606,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 7f64f2cfb941..66cc3cd189b8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -562,6 +562,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 7e4d94dc1f93..9abbf7dc6445 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 2e8aa21f3f55..bf139e95e99d 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 5e1b0a840cc7501383422eea4dd4facce27d02c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 15:54:27 +0100 Subject: [PATCH 02/50] 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 872f7bd6302e..f342689c3e3c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -669,6 +669,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 955f1a34342c..8d5557f1e723 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 634100e5fe46..11356a87bde7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -617,8 +617,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 66cc3cd189b8..b2f567dd83d8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -528,6 +528,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 942628519545..527376594559 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -379,6 +379,7 @@ object Checking { checkNoConflict(Lazy, Inline) 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 f5ef7b8bcd04bcb33cdb731bd60c0a72e273035b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:41:22 +0100 Subject: [PATCH 03/50] 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 1e64ff94d872..37dfd0329ae7 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 8d5557f1e723..5be9096e8314 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 b2f567dd83d8..658476f7b5c3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -743,8 +743,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 2403e066269ae9f0b0c3342dc5d889178c280c63 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:43:52 +0100 Subject: [PATCH 04/50] 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 f5060bea49b69a95fd1175bb2b0c339df588e6f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:44:14 +0100 Subject: [PATCH 05/50] 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 b7e7ef74cbfe1550043ec2c53ed097584df44857 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:16:17 +0100 Subject: [PATCH 06/50] 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 5be9096e8314..c830240e1991 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 a79210b2e1d3467395d9b9e64c0d46de898e9b8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:31:21 +0100 Subject: [PATCH 07/50] 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 2611d96df182..627672102dde 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -759,9 +759,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) @@ -770,12 +770,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)) @@ -807,8 +809,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 a11d1ada2e470cb403f1c9087403f54d33bcc66b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 14:16:48 +0100 Subject: [PATCH 08/50] 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 f342689c3e3c..76c810ec9c6b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -671,6 +671,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 37dfd0329ae7..b11f4b2e38cf 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 c830240e1991..608fc414871e 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 b7ea26776bd4..29e986fc6728 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 74d965ef48ac..0b969a4c1cc8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -163,9 +163,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 @@ -173,7 +170,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 11356a87bde7..387fc2b2cf32 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -617,8 +617,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 658476f7b5c3..38b1621077b2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -743,14 +743,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 d1019095edeb..f978fb2db2f3 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 312e2d4093db..9106772c0801 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 6be342739f75..30f04aee8ccb 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 4feb71d902ceead742c0a81c83554a469deed1d0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:02:04 +0100 Subject: [PATCH 09/50] 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 3240300d8f52..9d4cdeb52e0b 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 @@ -30,13 +31,14 @@ import StdNames._ * - 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" @@ -53,12 +55,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 def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { From 1f985da030c686558434a802664886194be530dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:43:24 +0100 Subject: [PATCH 10/50] 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 76c810ec9c6b..497c477b8adc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -527,6 +527,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") @@ -669,10 +671,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") @@ -1158,6 +1156,8 @@ class Definitions { AnyRefAlias, 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 b11f4b2e38cf..d288eb27fb82 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 608fc414871e..6b6a3d67a3a6 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 3fb11d2195c119da56924649e016dd59e4c477cf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:32:27 +0100 Subject: [PATCH 11/50] 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 6b6a3d67a3a6..8cc8481cca99 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 31f23a3327e5..1f25b7130958 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -393,7 +393,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)) @@ -401,7 +401,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) @@ -439,21 +439,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 f9b141b7997dc2f208abb50003cda308fef6622b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:57:18 +0100 Subject: [PATCH 12/50] 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 2aed6909d5ef..ef12510e7574 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 599b408bc26566e08f8dfecccf62d57149bc5f5c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:08:42 +0100 Subject: [PATCH 13/50] 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 cdc5e548510e125317ec13029f1fd8392fb48f3d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:10:15 +0100 Subject: [PATCH 14/50] 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 95a3d076e995d14bf1fdff8712df0172baef1573 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 08:51:34 +0100 Subject: [PATCH 15/50] 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 d288eb27fb82..2a61a68a47c2 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -394,6 +394,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 8cc8481cca99..9ce5d7c28432 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 38b1621077b2..70801fb10412 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -740,16 +740,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 9d4cdeb52e0b..317901edb0bb 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -66,7 +66,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 1d03fc0e030bd7d38e2ce7dd24304570e286e77c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 09:29:57 +0100 Subject: [PATCH 16/50] 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 2a61a68a47c2..4a57642996d9 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -246,6 +246,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val DEFAULT_CASE: N = "defaultCase$" @@ -394,7 +395,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 9ce5d7c28432..fa3f1e6ab097 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 317901edb0bb..14ece431c33b 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -64,17 +64,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 42934322e55c95660a6e77a913a7ee50e3d3c561 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 10:34:28 +0100 Subject: [PATCH 17/50] 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 497c477b8adc..c4cef42e5f0a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -527,7 +527,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 @@ -1156,7 +1155,6 @@ class Definitions { AnyRefAlias, 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 fa3f1e6ab097..3ecb977bf7da 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 387fc2b2cf32..36dfe4cd0977 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -619,7 +619,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 70801fb10412..e608e03cfefb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -742,7 +742,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 f978fb2db2f3..8e60515ffdae 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 8ac08ccef5f3ff30a267600da314db0b04e4b672 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 17:58:01 +0100 Subject: [PATCH 18/50] 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 c6bb46f61c82d99d79657a738a56a00ec8c2cd7f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 15:03:44 +0100 Subject: [PATCH 19/50] Bump minor Tasty version --- compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index fef25c11fbc4..180c14922588 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -228,7 +228,7 @@ object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) val MajorVersion = 4 - val MinorVersion = 0 + val MinorVersion = 1 /** Tags used to serialize names */ class NameTags { From b2e85a0cc34d9f73b7cd7f9c58bb36fe351a50e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 17:24:21 +0100 Subject: [PATCH 20/50] 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 c85cbcdf2622..d351aa318978 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 { @@ -1283,6 +1283,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 54db6540d49d..ac8ae2ab0875 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._ @@ -104,8 +105,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 } @@ -282,6 +284,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 0e283cd49e37..620051b8da9e 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 438b3ef9b279..f1c8eb5c1ac2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2087,8 +2087,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 7b6723746b09604e97b78ac78a3781058e3f1311 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 11:13:31 +0100 Subject: [PATCH 21/50] 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 | 12 ++++++-- .../dotty/tools/dotc/typer/TypeAssigner.scala | 30 ++++++++----------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d351aa318978..02202456a2da 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -508,7 +508,9 @@ object Types { case tp1 => tp1 }) case tp: TypeRef => - tp.denot.findMember(name, pre, excluded) + val mbr = tp.denot.findMember(name, pre, excluded) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) case tp: AppliedType => goApplied(tp) case tp: ThisType => @@ -2132,7 +2134,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 620051b8da9e..1ae290f91f2e 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 5de563ba982552c601035dae1f67339497c5898c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 11:14:31 +0100 Subject: [PATCH 22/50] 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 f6dffe4bb008c0cd0ef11c22e6e3b5a940cda1d7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Feb 2018 14:17:40 +0100 Subject: [PATCH 23/50] Add augment clauses The syntax is still open to discussion. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 57 +++++++++ compiler/src/dotty/tools/dotc/ast/untpd.scala | 7 ++ .../dotty/tools/dotc/config/Printers.scala | 1 + .../src/dotty/tools/dotc/core/StdNames.scala | 12 +- .../dotty/tools/dotc/parsing/Parsers.scala | 55 +++++--- .../src/dotty/tools/dotc/parsing/Tokens.scala | 5 +- docs/docs/internals/syntax.md | 20 ++- tests/neg/i2494.scala | 2 +- tests/pos/augment.scala | 118 ++++++++++++++++++ tests/pos/opaque-augment.scala | 35 ++++++ 10 files changed, 276 insertions(+), 36 deletions(-) create mode 100644 tests/pos/augment.scala create mode 100644 tests/pos/opaque-augment.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 30dad2f44847..fa362e4f0d86 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 @@ -749,6 +750,61 @@ object desugar { Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) } + /** augment extends { } } + * -> + * implicit class ($this: name ) + * extends { } + * + * augment extends { } } + * -> + * implicit class ($this: ) + * extends { } + * + * where + * + * = To$ where is first extended class name + * = Augmentation$ if no such exists + * = counter making prefix unique + * = references to + * = with each occurrence of unqualified `this` substituted by `$this`. + */ + def augmentation(tree: Augment)(implicit ctx: Context): Tree = { + val Augment(name, impl) = tree + val constr @ DefDef(_, tparams, vparamss, _, _) = impl.constr + var decorated: Tree = Ident(name) + if (tparams.nonEmpty) + decorated = + if (name.isEmpty) refOfDef(tparams.head) + else AppliedTypeTree(decorated, tparams.map(refOfDef)) + val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) + val constr1 = cpy.DefDef(constr)(vparamss = (firstParam :: Nil) :: vparamss) + 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 targetSuffix(tree: Tree): String = tree match { + case Apply(tycon, args) => targetSuffix(tycon) + case TypeApply(tycon, args) => targetSuffix(tycon) + case Select(pre, nme.CONSTRUCTOR) => targetSuffix(pre) + case New(tpt) => targetSuffix(tpt) + case AppliedTypeTree(tycon, _) => targetSuffix(tycon) + case tree: RefTree => "To" ++ tree.name.toString + case _ => str.Augmentation + } + val decoName: TypeName = impl.parents match { + case parent :: _ => name ++ targetSuffix(parent) + case _ => name ++ str.Augmentation + } + val icls = + TypeDef(UniqueName.fresh(decoName.toTermName).toTypeName, + cpy.Template(impl)(constr = constr1, body = substThis.transform(impl.body))) + .withFlags(Implicit) + desugr.println(i"desugar $name --> $icls") + classDef(icls) + } + 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 +813,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) + case tree: Augment => augmentation(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index dcfeba5842cd..f7ac7a72698b 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) } + /** augment name impl */ + case class Augment(name: TypeName, impl: Template) extends DefTree + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -411,6 +414,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 Augment(tree: Tree)(name: TypeName, impl: Template) = tree match { + case tree: Augment if (name eq tree.name) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Augment(name, 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 diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index beae798c324a..1dff187f0000 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 = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 4a57642996d9..d8a75659d314 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -42,6 +42,7 @@ object StdNames { final val AbstractFunction = "AbstractFunction" final val Tuple = "Tuple" final val Product = "Product" + final val Augmentation = "Augmentation" def sanitize(str: String) = str.replaceAll("""[<>]""", """\$""") } @@ -255,17 +256,6 @@ object StdNames { val FAKE_LOCAL_THIS: N = "this$" val LAZY_FIELD_OFFSET: N = "OFFSET$" val LAZY_SLOW_SUFFIX: N = "$lzycompute" - val UNIVERSE_BUILD_PREFIX: N = "$u.build." - val UNIVERSE_BUILD: N = "$u.build" - val UNIVERSE_PREFIX: N = "$u." - val UNIVERSE_SHORT: N = "$u" - val MIRROR_PREFIX: N = "$m." - val MIRROR_SHORT: N = "$m" - val MIRROR_UNTYPED: N = "$m$untyped" - val REIFY_FREE_PREFIX: N = "free$" - val REIFY_FREE_THIS_SUFFIX: N = "$this" - val REIFY_FREE_VALUE_SUFFIX: N = "$value" - val REIFY_SYMDEF_PREFIX: N = "symdef$" val OUTER: N = "$outer" val REFINE_CLASS: N = "" val ROOTPKG: N = "_root_" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9abbf7dc6445..9648d525f3a7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1831,8 +1831,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, ofAugmentation: Boolean = false): List[List[ValDef]] = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once var firstClauseOfCaseClass = ofCaseClass @@ -1878,7 +1880,7 @@ object Parsers { } } def paramClause(): List[ValDef] = inParens { - if (in.token == RPAREN) Nil + if (!ofAugmentation && in.token == RPAREN) Nil else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -1891,7 +1893,8 @@ object Parsers { } } funArgMods() - + if (ofAugmentation && !imods.is(Implicit)) + syntaxError(i"parameters of augment clause must be implicit") commaSeparated(() => param()) } } @@ -1901,7 +1904,7 @@ object Parsers { imods = EmptyModifiers paramClause() :: { firstClauseOfCaseClass = false - if (imods is Implicit) Nil else clauses() + if (imods.is(Implicit) || ofAugmentation) Nil else clauses() } } else Nil } @@ -2180,7 +2183,7 @@ object Parsers { } } - /** 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 +2191,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 +2200,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 +2209,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 +2226,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 +2272,19 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } + /** Augmentation ::= ‘augment’ (id | [id] ClassTypeParamClause) + * [[nl] ImplicitParamClause] TemplateClause + */ + def augmentation(): Augment = atPos(in.skipToken(), nameStart) { + val (name, tparams) = + if (isIdent) (ident().toTypeName, typeParamClauseOpt(ParamOwner.Class)) + else (tpnme.EMPTY, typeParamClause(ParamOwner.Class)) + val vparamss = paramClauses(name, ofAugmentation = true) + val constr = makeConstructor(tparams, vparamss) + val templ = templateClauseOpt(constr, bodyRequired = true) + Augment(name, templ) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2285,26 +2301,27 @@ 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) } @@ -2380,6 +2397,7 @@ object Parsers { * TemplateStat ::= Import * | Annotations Modifiers Def * | Annotations Modifiers Dcl + * | Augmentation * | Expr1 * | * EnumStat ::= TemplateStat @@ -2410,6 +2428,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == AUGMENT) + stats += augmentation() else if (isExprIntro) stats += expr1() else if (isDefIntro(modifierTokensOrCase)) @@ -2454,6 +2474,7 @@ object Parsers { * BlockStat ::= Import * | Annotations [implicit] [lazy] Def * | Annotations LocalModifiers TmplDef + * | Augmentation * | Expr1 * | */ @@ -2464,6 +2485,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == AUGMENT) + stats += augmentation() else if (isExprIntro) stats += expr(Location.InBlock) else if (isDefIntro(localModifierTokens)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 5becc9161309..426e4ffa8f11 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 AUGMENT = 66; enter(AUGMENT, "augment") /** 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, AUGMENT) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -239,7 +240,7 @@ object Tokens extends TokensCommon { /** Is token only legal as start of statement (eof also included)? */ final val mustStartStatTokens = defIntroTokens | modifierTokens | BitSet( - IMPORT, PACKAGE) + IMPORT, PACKAGE, AUGMENT) final val canStartStatTokens = canStartExpressionTokens | mustStartStatTokens | BitSet( AT, CASE) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index bf139e95e99d..4c93a7814fcb 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -120,7 +120,6 @@ ClassQualifier ::= ‘[’ id ‘]’ Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | InfixType -FunArgMods ::= { `implicit` | `erased` } FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | '(' TypedFunParam {',' TypedFunParam } ')' @@ -209,6 +208,7 @@ Block ::= {BlockStat semi} [BlockResult] BlockStat ::= Import | {Annotation} [‘implicit’ | ‘lazy’] Def | {Annotation} {LocalModifier} TmplDef + | Augmentation | Expr1 ForExpr ::= ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’) ForYield(enums, expr) @@ -241,6 +241,9 @@ PatVar ::= varid Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’ + +Augmentation ::= ‘augment’ (id | [id] ClsTypeParamClause) + [[nl] ImplicitParamClause] TemplateClause Augment(name, templ) ``` ### Type and Value Parameters @@ -261,6 +264,8 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ +ImplicitParamClause + ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’) ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param @@ -289,6 +294,8 @@ LocalModifier ::= ‘abstract’ | ‘opaque’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ (id | ‘this’) ‘]’ +FunArgMods ::= { `implicit` | `erased` } +ImplicitMods ::= `implicit` [`erased`] | `erased` `implicit` Annotation ::= ‘@’ SimpleType {ParArgumentExprs} Apply(tpe, args) @@ -330,12 +337,12 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | trait’) ClassDef | [‘case’] ‘object’ ObjectDef | `enum' EnumDef -ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) -ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat +ClassDef ::= id ClassConstr [TemplateClause] TypeDef(mods, name, 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) +TemplateClause ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args) @@ -348,6 +355,7 @@ TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} TemplateStat ::= Import | {Annotation [nl]} {Modifier} Def | {Annotation [nl]} {Modifier} Dcl + | Augmentation | Expr1 | SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) diff --git a/tests/neg/i2494.scala b/tests/neg/i2494.scala index 21c11242878d..21a169a2e58b 100644 --- a/tests/neg/i2494.scala +++ b/tests/neg/i2494.scala @@ -1,2 +1,2 @@ enum -object // error // error +object // error // error // error diff --git a/tests/pos/augment.scala b/tests/pos/augment.scala new file mode 100644 index 000000000000..91f2de70e9f8 --- /dev/null +++ b/tests/pos/augment.scala @@ -0,0 +1,118 @@ +import Predef.{any2stringadd => _, _} +object augments { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + augment Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + augment Circle extends HasArea { + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + augment List[T] { + def second = this.tail.head + } + + // cf implementation of Array#summ below + augment List[T <: Int] { + def maxx = (0 /: this)(_ `max` _) + } + + // cf implementation of Array#summ below + augment Array[T >: Int <: 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 + } + + augment Rectangle[T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic augments + + augment [T] { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic augments + + trait HasEql[T] { + def === (that: T): Boolean + } + + augment [T: Eql] extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment Rectangle[T: Eql] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } + + augment [T <: List[Int]] { + def summ = (0 /: this)(_ + _) + } + + augment [T <: Array[Int]] { + def summ = (0 /: this)(_ + _) + } + +// Generic augments with additional parameters + + augment [T <: List[List[U]], U] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + augment [T <: (U, U), U: Eql] { + def isSame = this._1 === this._2 + } +} + +import augments._ +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(r1 === r1) + println(r1 === r2) + println(List(1, 2, 3).second) + println(List(List(1), List(2, 3)).flattened) + println(List(List(1), List(2, 3)).flattened.maxx) + println(List(List(1), List(2, 3)).flattened.summ) + println(Array(1, 2, 3).maxx) + println(Array(1, 2, 3).summ) + println((2, 3).isSame) + println((3, 3).isSame) +} \ No newline at end of file diff --git a/tests/pos/opaque-augment.scala b/tests/pos/opaque-augment.scala new file mode 100644 index 000000000000..d7d5364b074d --- /dev/null +++ b/tests/pos/opaque-augment.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 + + augment 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] +} From 971e38500e471408940f95e230cb24fdb6b281ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 12:00:16 +0100 Subject: [PATCH 24/50] Allow for `type id` in type patterns Have `type T` as an alternate for lower-case `t` identifiers as variable binders in type patterns. Over time, we will deprecate the `t` form. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 50 +++++++++---- .../dotty/tools/dotc/parsing/Parsers.scala | 73 ++++++++++++++----- docs/docs/internals/syntax.md | 21 ++++-- tests/neg/i2494.scala | 4 +- tests/pos/typepats.scala | 13 ++++ 5 files changed, 116 insertions(+), 45 deletions(-) create mode 100644 tests/pos/typepats.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index fa362e4f0d86..3b64a9ca51d1 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -155,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) @@ -172,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 { @@ -744,6 +750,20 @@ object desugar { } } + def decomposeTypePattern(tree: Tree)(implicit ctx: Context): (Tree, List[TypeDef]) = { + val bindingsBuf = new ListBuffer[TypeDef] + val elimTypeDefs = new untpd.TreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case tree: TypeDef => + bindingsBuf += tree + Ident(tree.name).withPos(tree.pos) + case _ => + super.transform(tree) + } + } + (elimTypeDefs.transform(tree), bindingsBuf.toList) + } + /** Expand variable identifier x to x @ _ */ def patternVar(tree: Tree)(implicit ctx: Context) = { val Ident(name) = tree diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9648d525f3a7..952d37517dcd 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -323,6 +323,20 @@ object Parsers { finally inEnum = saved } + private[this] var inTypePattern = false + private[this] var inBindingTypePattern = false + private def withinTypePattern[T](binding: Boolean)(body: => T): T = { + val savedInType = inTypePattern + val savedInBinding = inBindingTypePattern + inTypePattern = true + if (binding) inBindingTypePattern = true + try body + finally { + inTypePattern = savedInType + inBindingTypePattern = savedInBinding + } + } + def migrationWarningOrError(msg: String, offset: Int = in.offset) = if (in.isScala2Mode) ctx.migrationWarning(msg, source atPos Position(offset)) @@ -917,7 +931,16 @@ object Parsers { else Nil first :: rest } - def typParser() = if (wildOK) typ() else toplevelTyp() + def typParser() = + if (in.token == TYPE && inTypePattern) + if (inBindingTypePattern) + typeParamCore(in.skipToken(), isConcreteOwner = true) + else + atPos(in.skipToken(), nameStart) { + Bind(ident().toTypeName, Ident(nme.WILDCARD)) + } + else if (wildOK) typ() + else toplevelTyp() if (namedOK && in.token == IDENTIFIER) typParser() match { case Ident(name) if in.token == EQUALS => @@ -998,7 +1021,7 @@ object Parsers { def typeDependingOn(location: Location.Value): Tree = if (location == Location.InParens) typ() - else if (location == Location.InPattern) refinedType() + else if (location == Location.InPattern) withinTypePattern(binding = false)(refinedType()) else infixType() /** Checks whether `t` is a wildcard type. @@ -1058,7 +1081,7 @@ object Parsers { * | PostfixExpr `match' `{' CaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] - * Ascription ::= `:' CompoundType + * Ascription ::= `:' InfixType * | `:' Annotation {Annotation} * | `:' `_' `*' */ @@ -1178,6 +1201,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 +1569,7 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() } else Nil - /** Pattern1 ::= PatVar Ascription + /** Pattern1 ::= PatVar PatternAscription * | Pattern2 */ def pattern1(): Tree = { @@ -1774,11 +1804,11 @@ object Parsers { /* -------- PARAMETERS ------------------------------------------- */ /** ClsTypeParamClause::= `[' ClsTypeParam {`,' ClsTypeParam} `]' - * ClsTypeParam ::= {Annotation} [`+' | `-'] - * id [HkTypeParamClause] TypeParamBounds + * ClsTypeParam ::= {Annotation} [`+' | `-'] TypeParamCore * * DefTypeParamClause::= `[' DefTypeParam {`,' DefTypeParam} `]' - * DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds + * DefTypeParam ::= {Annotation} TypeParamCore + * TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds * * TypTypeParamCaluse::= `[' TypTypeParam {`,' TypTypeParam} `]' * TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds @@ -1802,23 +1832,26 @@ object Parsers { else EmptyFlags } } - atPos(start, nameStart) { - val name = - if (isConcreteOwner || in.token != USCORE) ident().toTypeName - else { - in.nextToken() - WildcardParamName.fresh().toTypeName - } - val hkparams = typeParamClauseOpt(ParamOwner.TypeParam) - val bounds = - if (isConcreteOwner) typeParamBounds(name) - else typeBounds() - TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) - } + typeParamCore(start, isConcreteOwner).withMods(mods) } commaSeparated(() => typeParam()) } + /** TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds */ + def typeParamCore(start: Offset, isConcreteOwner: Boolean): TypeDef = atPos(start, nameStart) { + val name = + if (in.token == USCORE && !isConcreteOwner) { + in.nextToken() + WildcardParamName.fresh().toTypeName + } + else ident().toTypeName + val hkparams = typeParamClauseOpt(ParamOwner.TypeParam) + val bounds = + if (isConcreteOwner) typeParamBounds(name) + else typeBounds() + TypeDef(name, lambdaAbstract(hkparams, bounds)) + } + def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] = if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 4c93a7814fcb..a4ea59b5f8f4 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -120,6 +120,8 @@ ClassQualifier ::= ‘[’ id ‘]’ Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | InfixType + | ‘type’ TypeParamCore + (if inside a BindingTypePattern) FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | '(' TypedFunParam {',' TypedFunParam } ')' @@ -137,8 +139,10 @@ SimpleType ::= SimpleType TypeArgs | ‘_’ TypeBounds | Refinement RefinedTypeTree(EmptyTree, refinement) | SimpleLiteral SingletonTypeTree(l) -ArgTypes ::= Type {‘,’ Type} - | NamedTypeArg {‘,’ NamedTypeArg} +ArgTypes ::= ArgType {‘,’ ArgType} +ArgType ::= Type + | ‘type’ id + (if inside a TypePattern) FunArgType ::= Type | ‘=>’ Type PrefixOp(=>, t) ParamType ::= [‘=>’] ParamValueType @@ -225,7 +229,7 @@ CaseClauses ::= CaseClause { CaseClause } CaseClause ::= ‘case’ (Pattern [Guard] ‘=>’ Block | INT) CaseDef(pat, guard?, block) // block starts at => Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) -Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) +Pattern1 ::= PatVar ‘:’ TypePattern Bind(name, Typed(Ident(wildcard), tpe)) | Pattern2 Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) @@ -238,22 +242,23 @@ SimplePattern1 ::= Path | SimplePattern1 ‘.’ id PatVar ::= varid | ‘_’ +BindingTypePattern::= Type +TypePattern ::= RefinedType Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’ -Augmentation ::= ‘augment’ (id | [id] ClsTypeParamClause) +Augmentation ::= ‘augment’ BindingTypePattern [[nl] ImplicitParamClause] TemplateClause Augment(name, templ) ``` ### Type and Value Parameters ```ebnf ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ -ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) - id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) - +ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeParamCore TypeDef(Modifiers, name, tparams, bounds) DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds +DefTypeParam ::= {Annotation} TypeParamCore +TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds diff --git a/tests/neg/i2494.scala b/tests/neg/i2494.scala index 21a169a2e58b..d636e3f81d8c 100644 --- a/tests/neg/i2494.scala +++ b/tests/neg/i2494.scala @@ -1,2 +1,2 @@ -enum -object // error // error // error +enum // error: cyclic reference +object // error // error diff --git a/tests/pos/typepats.scala b/tests/pos/typepats.scala new file mode 100644 index 000000000000..df051fdc4842 --- /dev/null +++ b/tests/pos/typepats.scala @@ -0,0 +1,13 @@ +object Test { + val xs: Any = List(1, 2, 3) + xs match { + case xs: List[type T] => + } + trait I[T] + class C[T] extends I[T] + val y: I[Int] = new C[Int] + y match { + case _: C[type T] => + val x: T = 3 + } +} \ No newline at end of file From 3e55b6b616d2905a8b4f1380a5441530835ba213 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 12:28:10 +0100 Subject: [PATCH 25/50] Convert tests to use new type pattern syntax Convert many existing occurrences of `t` as a type pattern variable to `type t`. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 -- docs/docs/internals/syntax.md | 2 +- tests/neg-custom-args/i1754.scala | 2 +- tests/neg/gadt-eval.scala | 2 +- tests/pos/escapes2.scala | 2 +- tests/pos/exhaust_2.scala | 2 +- tests/pos/existentials-harmful.scala | 2 +- tests/pos/gadt-eval.scala | 2 +- tests/pos/i0290-type-bind.scala | 7 +++++-- tests/pos/i0306.scala | 4 ++-- tests/pos/i1365.scala | 3 +++ tests/pos/i947.scala | 4 ++-- tests/pos/t4070.scala | 4 ++-- tests/pos/t6205.scala | 6 +++--- tests/pos/t6275.scala | 2 +- tests/pos/t8023.scala | 2 +- tests/pos/t946.scala | 2 +- 17 files changed, 27 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 952d37517dcd..89d3f174481e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1805,14 +1805,12 @@ object Parsers { /** ClsTypeParamClause::= `[' ClsTypeParam {`,' ClsTypeParam} `]' * ClsTypeParam ::= {Annotation} [`+' | `-'] TypeParamCore - * * DefTypeParamClause::= `[' DefTypeParam {`,' DefTypeParam} `]' * DefTypeParam ::= {Annotation} TypeParamCore * TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds * * TypTypeParamCaluse::= `[' TypTypeParam {`,' TypTypeParam} `]' * TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds - * * HkTypeParamClause ::= `[' HkTypeParam {`,' HkTypeParam} `]' * HkTypeParam ::= {Annotation} ['+' | `-'] (id [HkTypePamClause] | _') TypeBounds */ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index a4ea59b5f8f4..8f093d1ec9c2 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -242,7 +242,6 @@ SimplePattern1 ::= Path | SimplePattern1 ‘.’ id PatVar ::= varid | ‘_’ -BindingTypePattern::= Type TypePattern ::= RefinedType Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) @@ -250,6 +249,7 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Augmentation ::= ‘augment’ BindingTypePattern [[nl] ImplicitParamClause] TemplateClause Augment(name, templ) +BindingTypePattern::= Type ``` ### Type and Value Parameters diff --git a/tests/neg-custom-args/i1754.scala b/tests/neg-custom-args/i1754.scala index 68f8f2edecb6..5171599e9460 100644 --- a/tests/neg-custom-args/i1754.scala +++ b/tests/neg-custom-args/i1754.scala @@ -2,7 +2,7 @@ case class One[T](fst: T) object Test { def bad[T](e: One[T]) = e match { - case foo: One[a] => + case foo: One[type A] => val t: T = e.fst val nok: Nothing = t // error } diff --git a/tests/neg/gadt-eval.scala b/tests/neg/gadt-eval.scala index ff113785a2fc..3bb5d58ad697 100644 --- a/tests/neg/gadt-eval.scala +++ b/tests/neg/gadt-eval.scala @@ -20,7 +20,7 @@ object Test { def eval2[T](e: Exp[T]): T = e match { case e: Lit => e.value - case e: Pair[t1, t2] => + case e: Pair[type t1, type t2] => (eval(e.fst), eval(e.fst)) // error: //-- [E007] Type Mismatch Error: tests/neg/gadt-eval.scala:24:6 ------------------ //24 | (eval(e.fst), eval(e.fst)) diff --git a/tests/pos/escapes2.scala b/tests/pos/escapes2.scala index b94066936a57..bc10b58ac6d8 100644 --- a/tests/pos/escapes2.scala +++ b/tests/pos/escapes2.scala @@ -1,5 +1,5 @@ object Test { class C3[T](val elem: T) class D3[T](val elemD: T) extends C3[T](elemD) - def f[T](x: C3[T]) = x match { case d: D3[t] => d.elemD } + def f[T](x: C3[T]) = x match { case d: D3[type X] => d.elemD } } diff --git a/tests/pos/exhaust_2.scala b/tests/pos/exhaust_2.scala index 4f4e47c43b5e..3751c6ed756c 100644 --- a/tests/pos/exhaust_2.scala +++ b/tests/pos/exhaust_2.scala @@ -44,7 +44,7 @@ object ExhaustivityWarnBugReportMinimal { val v2: (Some[_], Int) = (???, ???) v2 match { - case (x: Some[t], _) => + case (x: Some[type t], _) => } val v3: (Option[_], FoundNode[_]) = (???, ???) diff --git a/tests/pos/existentials-harmful.scala b/tests/pos/existentials-harmful.scala index 91dbd4dfda34..23cb75aa6e4b 100644 --- a/tests/pos/existentials-harmful.scala +++ b/tests/pos/existentials-harmful.scala @@ -47,7 +47,7 @@ object ExistentialsConsideredHarmful { // Type annotation on bc is required ... possible compiler bug? // val bc : BoxCarrier[_ <: Animal] = aBox match { val bc = aBox match { - case tb : TransportBox[a] => new BoxCarrier(tb) { + case tb : TransportBox[type A] => new BoxCarrier(tb) { def speed: Int = 12 } } diff --git a/tests/pos/gadt-eval.scala b/tests/pos/gadt-eval.scala index 2cebba3f378d..fd2305abd1c2 100644 --- a/tests/pos/gadt-eval.scala +++ b/tests/pos/gadt-eval.scala @@ -13,7 +13,7 @@ object Test { def eval2[T](e: Exp[T]): T = e match { case e: Lit => e.value - case e: Pair[t1, t2] => + case e: Pair[type T1, type T2] => (eval(e.fst), eval(e.snd)) } } diff --git a/tests/pos/i0290-type-bind.scala b/tests/pos/i0290-type-bind.scala index 83fdbbcc54d4..26a5330ba47d 100644 --- a/tests/pos/i0290-type-bind.scala +++ b/tests/pos/i0290-type-bind.scala @@ -3,6 +3,9 @@ object foo{ x match { case t: List[tt] => t.head.asInstanceOf[tt] } + x match { + case t: List[type tt] => t.head.asInstanceOf[tt] + } } object bar { @@ -12,8 +15,8 @@ object bar { val x: AnyRef = new C x match { - case x: C[u] => - def x: u = x + case x: C[type U] => + def x: U = x val s: Seq[_] = x } } diff --git a/tests/pos/i0306.scala b/tests/pos/i0306.scala index 5a242fa83d91..dc38593c11dd 100644 --- a/tests/pos/i0306.scala +++ b/tests/pos/i0306.scala @@ -5,8 +5,8 @@ object bar { val x: AnyRef = new C val y = x match { - case x: C[u] => - def xx: u = xx + case x: C[type U] => + def xx: U = xx xx } diff --git a/tests/pos/i1365.scala b/tests/pos/i1365.scala index e7d47da4b759..31d6c370aeb4 100644 --- a/tests/pos/i1365.scala +++ b/tests/pos/i1365.scala @@ -10,4 +10,7 @@ class Test[A] { def g(cmd: Message[A]): Unit = cmd match { case s: Script[z] => s.iterator.foreach(x => g(x)) } + def h(cmd: Message[A]): Unit = cmd match { + case s: Script[type Z] => s.iterator.foreach(x => g(x)) + } } diff --git a/tests/pos/i947.scala b/tests/pos/i947.scala index 0f2d9e77583a..f6a4dd686051 100644 --- a/tests/pos/i947.scala +++ b/tests/pos/i947.scala @@ -6,8 +6,8 @@ object Test { override def equals(other: Any) = other match { case o: c => x == o.x - case xs: List[c] => false - case ys: List[d18383] => false + case xs: List[type C] => false + case ys: List[type d18383] => false case _ => false } diff --git a/tests/pos/t4070.scala b/tests/pos/t4070.scala index a9777f02ed20..2dcb8c1a6061 100644 --- a/tests/pos/t4070.scala +++ b/tests/pos/t4070.scala @@ -1,7 +1,7 @@ package a { // method before classes trait Foo { - def crash(x: Dingus[_]): Unit = x match { case m: Bippy[tv] => () } + def crash(x: Dingus[_]): Unit = x match { case m: Bippy[type tv] => () } class Dingus[T] class Bippy[CC[X] <: Seq[X]]() extends Dingus[CC[Int]] @@ -14,7 +14,7 @@ package b { class Dingus[T] class Bippy[CC[X] <: Seq[X]]() extends Dingus[CC[Int]] - def crash(x: Dingus[_]): Unit = x match { case m: Bippy[tv] => () } + def crash(x: Dingus[_]): Unit = x match { case m: Bippy[type tv] => () } } } diff --git a/tests/pos/t6205.scala b/tests/pos/t6205.scala index 52078bd5f46f..4246953ff939 100644 --- a/tests/pos/t6205.scala +++ b/tests/pos/t6205.scala @@ -2,8 +2,8 @@ class A[T] class Test1 { def x(backing: Map[A[_], Any]) = - for( (k: A[kt], v) <- backing) - yield (k: A[kt]) + for( (k: A[type KT], v) <- backing) + yield (k: A[KT]) } // this tests same thing as above, but independent of library classes, @@ -13,6 +13,6 @@ class Mapped[A] { def map[T](f: Holder[A] => T): Iterable[T] = ??? } class Test2 { def works(backing: Mapped[A[_]]): Iterable[A[_]] = backing.map(x => - x match {case Holder(k: A[kt]) => (k: A[kt])} + x match {case Holder(k: A[type KT]) => (k: A[KT])} ) } diff --git a/tests/pos/t6275.scala b/tests/pos/t6275.scala index 6b5ec7dcebfb..2ece602a1c0b 100644 --- a/tests/pos/t6275.scala +++ b/tests/pos/t6275.scala @@ -5,7 +5,7 @@ final class B[T] extends A[T] object ParsedAxis { type BI = B[Int] - def f1(a: A[Int]) = a match { case b: B[Int] => 3 } + def f1(a: A[Int]) = a match { case b: B[type Int] => 3 } def f2(a: A[Int]) = a match { case b: BI => 3 } def f3(a: A[Int]) = a match { case b: B[t] => 3 } } diff --git a/tests/pos/t8023.scala b/tests/pos/t8023.scala index 66d478abd51e..fcfa0bba210d 100644 --- a/tests/pos/t8023.scala +++ b/tests/pos/t8023.scala @@ -3,7 +3,7 @@ class D[K] object Test3 { def foo = (null: Any) match { - case a: C[k] => new C[k]() // this one worked before as the info of `A` was complete + case a: C[type K] => new C[K]() // this one worked before as the info of `A` was complete // () } } diff --git a/tests/pos/t946.scala b/tests/pos/t946.scala index c4bd6e9ba415..d256ce6b3abe 100644 --- a/tests/pos/t946.scala +++ b/tests/pos/t946.scala @@ -3,6 +3,6 @@ object pmbugbounds { class Foo[t <: Bar] {} (new Foo[Bar]) match { - case _ : Foo[x] => null + case _ : Foo[type X] => null } } From 6d2cbba9c84a4d64748c49bf76f77ea73d525c65 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 13:22:37 +0100 Subject: [PATCH 26/50] Base augment syntax on type patterns --- .../src/dotty/tools/dotc/ast/Desugar.scala | 57 ++++++++++--------- compiler/src/dotty/tools/dotc/ast/untpd.scala | 10 ++-- .../dotty/tools/dotc/parsing/Parsers.scala | 15 +++-- docs/docs/internals/syntax.md | 2 +- tests/pos/augment.scala | 32 ++++------- 5 files changed, 54 insertions(+), 62 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3b64a9ca51d1..b605cf24c38c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -750,9 +750,15 @@ object desugar { } } + /** Expand variable identifier x to x @ _ */ + def patternVar(tree: Tree)(implicit ctx: Context) = { + val Ident(name) = tree + Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) + } + def decomposeTypePattern(tree: Tree)(implicit ctx: Context): (Tree, List[TypeDef]) = { val bindingsBuf = new ListBuffer[TypeDef] - val elimTypeDefs = new untpd.TreeMap { + val elimTypeDefs = new untpd.UntypedTreeMap { override def transform(tree: Tree)(implicit ctx: Context) = tree match { case tree: TypeDef => bindingsBuf += tree @@ -764,12 +770,6 @@ object desugar { (elimTypeDefs.transform(tree), bindingsBuf.toList) } - /** Expand variable identifier x to x @ _ */ - def patternVar(tree: Tree)(implicit ctx: Context) = { - val Ident(name) = tree - Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) - } - /** augment extends { } } * -> * implicit class ($this: name ) @@ -789,33 +789,36 @@ object desugar { * = with each occurrence of unqualified `this` substituted by `$this`. */ def augmentation(tree: Augment)(implicit ctx: Context): Tree = { - val Augment(name, impl) = tree - val constr @ DefDef(_, tparams, vparamss, _, _) = impl.constr - var decorated: Tree = Ident(name) - if (tparams.nonEmpty) - decorated = - if (name.isEmpty) refOfDef(tparams.head) - else AppliedTypeTree(decorated, tparams.map(refOfDef)) + val Augment(augmented, impl) = tree + val constr @ DefDef(_, Nil, vparamss, _, _) = impl.constr + val (decorated, bindings) = decomposeTypePattern(augmented) val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) - val constr1 = cpy.DefDef(constr)(vparamss = (firstParam :: Nil) :: vparamss) + val constr1 = + cpy.DefDef(constr)( + tparams = bindings.map(_.withFlags(Param | Private | Local)), + vparamss = (firstParam :: Nil) :: vparamss) 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 targetSuffix(tree: Tree): String = tree match { - case Apply(tycon, args) => targetSuffix(tycon) - case TypeApply(tycon, args) => targetSuffix(tycon) - case Select(pre, nme.CONSTRUCTOR) => targetSuffix(pre) - case New(tpt) => targetSuffix(tpt) - case AppliedTypeTree(tycon, _) => targetSuffix(tycon) - case tree: RefTree => "To" ++ tree.name.toString - case _ => str.Augmentation - } - val decoName: TypeName = impl.parents match { - case parent :: _ => name ++ targetSuffix(parent) - case _ => name ++ str.Augmentation + val decoName = { + def clsName(tree: Tree): String = tree match { + case Apply(tycon, args) => clsName(tycon) + case TypeApply(tycon, args) => clsName(tycon) + case Select(pre, nme.CONSTRUCTOR) => clsName(pre) + case New(tpt) => clsName(tpt) + case AppliedTypeTree(tycon, _) => clsName(tycon) + case tree: RefTree if tree.name.isTypeName => tree.name.toString + case _ => "" + } + val fromName = clsName(augmented) + val toName = impl.parents match { + case parent :: _ if !clsName(parent).isEmpty => clsName(parent) + case _ => str.Augmentation + } + fromName ++ toName } val icls = TypeDef(UniqueName.fresh(decoName.toTermName).toTypeName, diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index f7ac7a72698b..e7ea25364d08 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,8 +40,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } - /** augment name impl */ - case class Augment(name: TypeName, impl: Template) extends DefTree + /** augment augmented impl */ + case class Augment(augmented: Tree, impl: Template) extends DefTree case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -414,9 +414,9 @@ 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 Augment(tree: Tree)(name: TypeName, impl: Template) = tree match { - case tree: Augment if (name eq tree.name) && (impl eq tree.impl) => tree - case _ => finalize(tree, untpd.Augment(name, impl)) + def Augment(tree: Tree)(augmented: Tree, impl: Template) = tree match { + case tree: Augment if (augmented eq tree.augmented) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Augment(augmented, impl)) } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 89d3f174481e..9f9a4bf3f9f8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2303,17 +2303,16 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } - /** Augmentation ::= ‘augment’ (id | [id] ClassTypeParamClause) - * [[nl] ImplicitParamClause] TemplateClause + /** Augmentation ::= ‘augment’ BindingTypePattern + * [[nl] ImplicitParamClause] TemplateClause + * BindingTypePattern ::= AnnotType */ def augmentation(): Augment = atPos(in.skipToken(), nameStart) { - val (name, tparams) = - if (isIdent) (ident().toTypeName, typeParamClauseOpt(ParamOwner.Class)) - else (tpnme.EMPTY, typeParamClause(ParamOwner.Class)) - val vparamss = paramClauses(name, ofAugmentation = true) - val constr = makeConstructor(tparams, vparamss) + val augmented = withinTypePattern(binding = true)(annotType()) + val vparamss = paramClauses(tpnme.EMPTY, ofAugmentation = true).take(1) + val constr = makeConstructor(Nil, vparamss) val templ = templateClauseOpt(constr, bodyRequired = true) - Augment(name, templ) + Augment(augmented, templ) } /* -------- TEMPLATES ------------------------------------------- */ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 8f093d1ec9c2..f518786a6454 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -249,7 +249,7 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Augmentation ::= ‘augment’ BindingTypePattern [[nl] ImplicitParamClause] TemplateClause Augment(name, templ) -BindingTypePattern::= Type +BindingTypePattern::= AnnotType ``` ### Type and Value Parameters diff --git a/tests/pos/augment.scala b/tests/pos/augment.scala index 91f2de70e9f8..86259fd5bffd 100644 --- a/tests/pos/augment.scala +++ b/tests/pos/augment.scala @@ -21,17 +21,17 @@ object augments { // Generic trait implementations - augment List[T] { + augment List[type T] { def second = this.tail.head } - // cf implementation of Array#summ below - augment List[T <: Int] { +// Specific trait implementations + + augment List[Int] { def maxx = (0 /: this)(_ `max` _) } - // cf implementation of Array#summ below - augment Array[T >: Int <: Int] { + augment Array[Int] { def maxx = (0 /: this)(_ `max` _) } @@ -43,13 +43,13 @@ object augments { def eql (x: T, y: T): Boolean } - augment Rectangle[T: Eql] { + augment Rectangle[type T: Eql] { def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) } // Simple generic augments - augment [T] { + augment (type T) { def ~[U](that: U): (T, U) = (this, that) } @@ -59,11 +59,11 @@ object augments { def === (that: T): Boolean } - augment [T: Eql] extends HasEql[T] { + augment (type T: Eql) extends HasEql[T] { def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) } - augment Rectangle[T: Eql] { + augment Rectangle[type T: Eql] { def === (that: Rectangle[T]) = this.x === that.x && this.y === that.y && @@ -71,21 +71,13 @@ object augments { this.height == that.height } - augment [T <: List[Int]] { - def summ = (0 /: this)(_ + _) - } - - augment [T <: Array[Int]] { - def summ = (0 /: this)(_ + _) - } - // Generic augments with additional parameters - augment [T <: List[List[U]], U] { + augment List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } - augment [T <: (U, U), U: Eql] { + augment (type T: Eql, T) { def isSame = this._1 === this._2 } } @@ -110,9 +102,7 @@ object Test extends App { println(List(1, 2, 3).second) println(List(List(1), List(2, 3)).flattened) println(List(List(1), List(2, 3)).flattened.maxx) - println(List(List(1), List(2, 3)).flattened.summ) println(Array(1, 2, 3).maxx) - println(Array(1, 2, 3).summ) println((2, 3).isSame) println((3, 3).isSame) } \ No newline at end of file From cb5ae514f55da2818014b0e032d8482954185958 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 13:33:15 +0100 Subject: [PATCH 27/50] Fix typo --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index b605cf24c38c..c0d5f7cffba4 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -824,7 +824,7 @@ object desugar { TypeDef(UniqueName.fresh(decoName.toTermName).toTypeName, cpy.Template(impl)(constr = constr1, body = substThis.transform(impl.body))) .withFlags(Implicit) - desugr.println(i"desugar $name --> $icls") + desugr.println(i"desugar $augmented --> $icls") classDef(icls) } From dc057182f61c43e161975135df6ec0731f8aaad2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 14:02:05 +0100 Subject: [PATCH 28/50] Make annotation names compilation-order independent --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 6 ++++-- tests/pos/augment.scala | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c0d5f7cffba4..6c4abbf986a8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -811,14 +811,16 @@ object desugar { case New(tpt) => clsName(tpt) case AppliedTypeTree(tycon, _) => clsName(tycon) case tree: RefTree if tree.name.isTypeName => tree.name.toString + case Parens(tree) => clsName(tree) + case tree: TypeDef => tree.name.toString case _ => "" } val fromName = clsName(augmented) val toName = impl.parents match { - case parent :: _ if !clsName(parent).isEmpty => clsName(parent) + case parent :: _ if !clsName(parent).isEmpty => "To" + clsName(parent) case _ => str.Augmentation } - fromName ++ toName + s"${fromName}${toName}_in_${ctx.owner.topLevelClass.flatName}" } val icls = TypeDef(UniqueName.fresh(decoName.toTermName).toTypeName, diff --git a/tests/pos/augment.scala b/tests/pos/augment.scala index 86259fd5bffd..492c2b9ab2b9 100644 --- a/tests/pos/augment.scala +++ b/tests/pos/augment.scala @@ -63,7 +63,7 @@ object augments { def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) } - augment Rectangle[type T: Eql] { + augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { def === (that: Rectangle[T]) = this.x === that.x && this.y === that.y && From 958e67a3c91160b341d3c503a7a36fbcbf695089 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 14:36:04 +0100 Subject: [PATCH 29/50] Require that non-extending augments only add extension methods --- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 13 +- docs/docs/internals/syntax.md | 4 +- tests/neg/augment.scala | 111 ++++++++++++++++++ 4 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 tests/neg/augment.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 02202456a2da..b8bd21b85f34 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2209,7 +2209,7 @@ object Types { if (ctx.erasedTypes) tref else cls.info match { case cinfo: ClassInfo => cinfo.selfType - case cinfo: ErrorType if ctx.mode.is(Mode.Interactive) => cinfo + case _: ErrorType | NoType if ctx.mode.is(Mode.Interactive) => cls.info // can happen in IDE if `cls` is stale } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9f9a4bf3f9f8..914b144fbbe6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2304,14 +2304,25 @@ object Parsers { } /** Augmentation ::= ‘augment’ BindingTypePattern - * [[nl] ImplicitParamClause] TemplateClause + * [[nl] ImplicitParamClause] Additions * BindingTypePattern ::= AnnotType + * Additions ::= ‘extends’ Template + * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ */ def augmentation(): Augment = atPos(in.skipToken(), nameStart) { val augmented = withinTypePattern(binding = true)(annotType()) val vparamss = paramClauses(tpnme.EMPTY, ofAugmentation = true).take(1) val constr = makeConstructor(Nil, vparamss) + val isSimpleExtension = in.token != EXTENDS val templ = templateClauseOpt(constr, bodyRequired = true) + if (isSimpleExtension) { + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + } Augment(augmented, templ) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f518786a6454..1d207c94e29f 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -248,7 +248,9 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’ Augmentation ::= ‘augment’ BindingTypePattern - [[nl] ImplicitParamClause] TemplateClause Augment(name, templ) + [[nl] ImplicitParamClause] AugmentClause Augment(name, templ) +AugmentClause ::= ‘extends’ Template + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ BindingTypePattern::= AnnotType ``` diff --git a/tests/neg/augment.scala b/tests/neg/augment.scala new file mode 100644 index 000000000000..9e738e051eb5 --- /dev/null +++ b/tests/neg/augment.scala @@ -0,0 +1,111 @@ +import Predef.{any2stringadd => _, _} +object augments { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + augment Circle { + def circumference = this.radius * math.Pi * 2 + private val p = math.Pi // error: `def` expected + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + augment Circle extends HasArea { + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + augment List[type T] { + type I = Int // error: `def` expected + def second = this.tail.head + } + +// Specific trait implementations + + augment List[Int] { + import java.lang._ + def maxx = (0 /: this)(_ `max` _) + } + + augment 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 + } + + augment Rectangle[type T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic augments + + augment (type T) { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic augments + + trait HasEql[T] { + def === (that: T): Boolean + } + + augment (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } + +// Generic augments with additional parameters + + augment List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + augment (type T: Eql, T) { + def isSame = this._1 === this._2 + } +} + +import augments._ +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(r1 === r1) + println(r1 === r2) + 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((3, 3).isSame) +} \ No newline at end of file From f8b661d871ed7d2959781574164ca8e39ff124e5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 17:13:36 +0100 Subject: [PATCH 30/50] Implement extension methods as implicit value classes --- .../src/dotty/tools/dotc/ast/Desugar.scala | 108 ++++++++++++------ tests/neg/augment.scala | 4 +- tests/pos/augment.scala | 12 ++ 3 files changed, 88 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 6c4abbf986a8..df57ee6484f9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -300,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 { @@ -770,51 +771,47 @@ object desugar { (elimTypeDefs.transform(tree), bindingsBuf.toList) } - /** augment extends { } } + /** augment extends { } } * -> - * implicit class ($this: name ) - * extends { } - * - * augment extends { } } - * -> - * implicit class ($this: ) + * implicit class ($this: ) * extends { } * * where * - * = To$ where is first extended class name - * = Augmentation$ if no such exists + * (, ) = decomposeTypePattern() + * (, ) = desugarTypeBindings() + * = concatenated with in one clause + * = To_in_$$ where is first extended class name + * + * = Augmentation_in_$$ if no such exists + * = underlying type name of + * = flat name of enclosing toplevel class * = counter making prefix unique - * = references to * = with each occurrence of unqualified `this` substituted by `$this`. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * augment { } + * -> + * implicit class ($this: ) + * extends AnyVal { } + * + * where + * + * = where each method definition gets as last parameter section. + * , are as above. */ def augmentation(tree: Augment)(implicit ctx: Context): Tree = { val Augment(augmented, impl) = tree - val constr @ DefDef(_, Nil, vparamss, _, _) = impl.constr + val isSimpleExtension = + impl.parents.isEmpty && + impl.self.isEmpty && + impl.body.forall(_.isInstanceOf[DefDef]) val (decorated, bindings) = decomposeTypePattern(augmented) - val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) - val constr1 = - cpy.DefDef(constr)( - tparams = bindings.map(_.withFlags(Param | Private | Local)), - vparamss = (firstParam :: Nil) :: vparamss) - 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) - } - } + val (typeParams, evidenceParams) = + desugarTypeBindings(bindings, forPrimaryConstructor = !isSimpleExtension) val decoName = { - def clsName(tree: Tree): String = tree match { - case Apply(tycon, args) => clsName(tycon) - case TypeApply(tycon, args) => clsName(tycon) - case Select(pre, nme.CONSTRUCTOR) => clsName(pre) - case New(tpt) => clsName(tpt) - case AppliedTypeTree(tycon, _) => clsName(tycon) - case tree: RefTree if tree.name.isTypeName => tree.name.toString - case Parens(tree) => clsName(tree) - case tree: TypeDef => tree.name.toString - case _ => "" - } + def clsName(tree: Tree): String = leadingName("", tree) val fromName = clsName(augmented) val toName = impl.parents match { case parent :: _ if !clsName(parent).isEmpty => "To" + clsName(parent) @@ -822,14 +819,57 @@ object desugar { } s"${fromName}${toName}_in_${ctx.owner.topLevelClass.flatName}" } + + val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) + var constr1 = + cpy.DefDef(impl.constr)( + tparams = typeParams.map(_.withFlags(Param | Private | Local)), + vparamss = (firstParam :: Nil) :: impl.constr.vparamss) + var parents1 = impl.parents + var body1 = substThis.transform(impl.body) + if (isSimpleExtension) { + constr1 = cpy.DefDef(constr1)(vparamss = constr1.vparamss.take(1)) + parents1 = ref(defn.AnyValType) :: Nil + body1 = 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) + } + } + else + constr1 = addEvidenceParams(constr1, evidenceParams) + val icls = TypeDef(UniqueName.fresh(decoName.toTermName).toTypeName, - cpy.Template(impl)(constr = constr1, body = substThis.transform(impl.body))) + cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) .withFlags(Implicit) desugr.println(i"desugar $augmented --> $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) + } + } + + private val leadingName = new UntypedTreeAccumulator[String] { + override def apply(x: String, tree: Tree)(implicit ctx: Context): String = + if (x.isEmpty) + tree match { + case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre) + case tree: RefTree if tree.name.isTypeName => tree.name.toString + case tree: TypeDef => tree.name.toString + case tree: Tuple => "Tuple" + case tree: Function => "Function" + case _ => foldOver(x, tree) + } + else x + } + 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 diff --git a/tests/neg/augment.scala b/tests/neg/augment.scala index 9e738e051eb5..a06312796a54 100644 --- a/tests/neg/augment.scala +++ b/tests/neg/augment.scala @@ -29,8 +29,8 @@ object augments { // Specific trait implementations - augment List[Int] { - import java.lang._ + augment List[Int] { self => // error: `def` expected + import java.lang._ // error: `def` expected def maxx = (0 /: this)(_ `max` _) } diff --git a/tests/pos/augment.scala b/tests/pos/augment.scala index 492c2b9ab2b9..8a648fc7113e 100644 --- a/tests/pos/augment.scala +++ b/tests/pos/augment.scala @@ -47,6 +47,10 @@ object augments { def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) } + augment Rectangle[type T](implicit ev: Eql[T]) { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(this.width, this.height) + } + // Simple generic augments augment (type T) { @@ -63,6 +67,10 @@ object augments { def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) } + augment (type T)(implicit ev: Eql[T]) { + def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { def === (that: Rectangle[T]) = this.x === that.x && @@ -97,8 +105,12 @@ object Test extends App { 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) From b90acc1d87b5a5340e12c07740c77b1d3fb87c47 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 27 Feb 2018 18:08:05 +0100 Subject: [PATCH 31/50] Partially fix idempotency of static method order Some source of nondeterminism remains in the backend. --- compiler/src/dotty/tools/dotc/transform/MoveStatics.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala index 484e538e0bf4..98a1e57d138b 100644 --- a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala +++ b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala @@ -34,7 +34,8 @@ class MoveStatics extends MiniPhase with SymTransformer { override def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] = { if (ctx.owner.is(Flags.Package)) { val (classes, others) = trees.partition(x => x.isInstanceOf[TypeDef] && x.symbol.isClass) - val pairs = classes.groupBy(_.symbol.name.stripModuleClassSuffix).asInstanceOf[Map[Name, List[TypeDef]]] + // TODO make a groupBy that builds linked maps + val pairs = classes.groupBy(_.symbol.name.stripModuleClassSuffix).asInstanceOf[Map[Name, List[TypeDef]]].toList.sortBy(_._1.toString) def rebuild(orig: TypeDef, newBody: List[Tree]): Tree = { if (orig eq null) return EmptyTree @@ -73,7 +74,7 @@ class MoveStatics extends MiniPhase with SymTransformer { if (classes.head.symbol.is(Flags.Module)) move(classes.head, null) else List(rebuild(classes.head, classes.head.rhs.asInstanceOf[Template].body)) else move(classes.head, classes.tail.head) - Trees.flatten(newPairs.toList.flatten ++ others) + Trees.flatten(newPairs.flatten ++ others) } else trees } } From 55405d83c24edd5bdc869687e60705c121054da4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 18:57:38 +0100 Subject: [PATCH 32/50] Allow labels naming an augment --- .../src/dotty/tools/dotc/ast/Desugar.scala | 31 +++++++++++-------- compiler/src/dotty/tools/dotc/ast/untpd.scala | 10 +++--- .../src/dotty/tools/dotc/core/StdNames.scala | 1 - .../dotty/tools/dotc/parsing/Parsers.scala | 9 ++++-- docs/docs/internals/syntax.md | 2 +- tests/neg/augment.scala | 13 +++++--- tests/run/augment.check | 16 ++++++++++ tests/{pos => run}/augment.scala | 16 +++++++--- 8 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 tests/run/augment.check rename tests/{pos => run}/augment.scala (88%) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index df57ee6484f9..753a64e20fe6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -771,7 +771,7 @@ object desugar { (elimTypeDefs.transform(tree), bindingsBuf.toList) } - /** augment extends { } } + /** augment [ @] extends { } } * -> * implicit class ($this: ) * extends { } @@ -781,7 +781,8 @@ object desugar { * (, ) = decomposeTypePattern() * (, ) = desugarTypeBindings() * = concatenated with in one clause - * = To_in_$$ where is first extended class name + * = if there is a `id @` binding + * = To_in_$$ where is first extended class name * * = Augmentation_in_$$ if no such exists * = underlying type name of @@ -791,7 +792,7 @@ object desugar { * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * - * augment { } + * augment [ @] { } * -> * implicit class ($this: ) * extends AnyVal { } @@ -802,7 +803,7 @@ object desugar { * , are as above. */ def augmentation(tree: Augment)(implicit ctx: Context): Tree = { - val Augment(augmented, impl) = tree + val Augment(id, augmented, impl) = tree val isSimpleExtension = impl.parents.isEmpty && impl.self.isEmpty && @@ -810,14 +811,18 @@ object desugar { val (decorated, bindings) = decomposeTypePattern(augmented) val (typeParams, evidenceParams) = desugarTypeBindings(bindings, forPrimaryConstructor = !isSimpleExtension) - val decoName = { - def clsName(tree: Tree): String = leadingName("", tree) - val fromName = clsName(augmented) - val toName = impl.parents match { - case parent :: _ if !clsName(parent).isEmpty => "To" + clsName(parent) - case _ => str.Augmentation - } - s"${fromName}${toName}_in_${ctx.owner.topLevelClass.flatName}" + val decoName = id match { + case Ident(name) => + name.asTypeName + case EmptyTree => + def clsName(tree: Tree): String = leadingName("", tree) + val fromName = clsName(augmented) + val toName = impl.parents match { + case parent :: _ if !clsName(parent).isEmpty => "To" + clsName(parent) + case _ => "Augmentation" + } + val prefix = s"${fromName}${toName}_in_${ctx.owner.topLevelClass.flatName}" + UniqueName.fresh(prefix.toTermName).toTypeName } val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) @@ -842,7 +847,7 @@ object desugar { constr1 = addEvidenceParams(constr1, evidenceParams) val icls = - TypeDef(UniqueName.fresh(decoName.toTermName).toTypeName, + TypeDef(decoName, cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) .withFlags(Implicit) desugr.println(i"desugar $augmented --> $icls") diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e7ea25364d08..6bf80f274804 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,8 +40,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } - /** augment augmented impl */ - case class Augment(augmented: Tree, impl: Template) extends DefTree + /** augment id @ augmented impl */ + case class Augment(id: Tree, augmented: Tree, impl: Template) extends DefTree case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -414,9 +414,9 @@ 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 Augment(tree: Tree)(augmented: Tree, impl: Template) = tree match { - case tree: Augment if (augmented eq tree.augmented) && (impl eq tree.impl) => tree - case _ => finalize(tree, untpd.Augment(augmented, impl)) + def Augment(tree: Tree)(id: Tree, augmented: Tree, impl: Template) = tree match { + case tree: Augment if (id eq tree.id) && (augmented eq tree.augmented) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Augment(id, augmented, impl)) } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index d8a75659d314..ea1bf83e2c67 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -42,7 +42,6 @@ object StdNames { final val AbstractFunction = "AbstractFunction" final val Tuple = "Tuple" final val Product = "Product" - final val Augmentation = "Augmentation" def sanitize(str: String) = str.replaceAll("""[<>]""", """\$""") } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 914b144fbbe6..bbdf9c0bede1 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2303,13 +2303,18 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } - /** Augmentation ::= ‘augment’ BindingTypePattern + /** Augmentation ::= ‘augment’ [id @] BindingTypePattern * [[nl] ImplicitParamClause] Additions * BindingTypePattern ::= AnnotType * Additions ::= ‘extends’ Template * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ */ def augmentation(): Augment = atPos(in.skipToken(), nameStart) { + var id: Tree = EmptyTree + if (isIdent && lookaheadIn(AT)) { + id = typeIdent() + in.nextToken() + } val augmented = withinTypePattern(binding = true)(annotType()) val vparamss = paramClauses(tpnme.EMPTY, ofAugmentation = true).take(1) val constr = makeConstructor(Nil, vparamss) @@ -2323,7 +2328,7 @@ object Parsers { checkDef(templ.self) templ.body.foreach(checkDef) } - Augment(augmented, templ) + Augment(id, augmented, templ) } /* -------- TEMPLATES ------------------------------------------- */ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 1d207c94e29f..0c3d4b5f66b3 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -247,7 +247,7 @@ Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’ -Augmentation ::= ‘augment’ BindingTypePattern +Augmentation ::= ‘augment’ [id ‘@’] BindingTypePattern [[nl] ImplicitParamClause] AugmentClause Augment(name, templ) AugmentClause ::= ‘extends’ Template | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ diff --git a/tests/neg/augment.scala b/tests/neg/augment.scala index a06312796a54..9efc7083ce45 100644 --- a/tests/neg/augment.scala +++ b/tests/neg/augment.scala @@ -73,19 +73,24 @@ object augments { this.width == that.width && this.height == that.height } +} -// Generic augments with additional parameters +object augments2 { + import augments.Eql + // Nested generic arguments - augment List[List[type U]] { + augment flatLists @ List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } - augment (type T: Eql, T) { - def isSame = this._1 === this._2 + augment samePairs @ (type T: Eql, T) { + def isSame: Boolean = this._1 === this._2 // error: === is not a member } + } import augments._ +import augments2._ object Test extends App { val c = Circle(0, 1, 2) println(c.area) diff --git a/tests/run/augment.check b/tests/run/augment.check new file mode 100644 index 000000000000..748a6ca11e55 --- /dev/null +++ b/tests/run/augment.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/pos/augment.scala b/tests/run/augment.scala similarity index 88% rename from tests/pos/augment.scala rename to tests/run/augment.scala index 8a648fc7113e..73318049d9e8 100644 --- a/tests/pos/augment.scala +++ b/tests/run/augment.scala @@ -63,7 +63,7 @@ object augments { def === (that: T): Boolean } - augment (type T: Eql) extends HasEql[T] { + augment eqlToHasEql @ (type T: Eql) extends HasEql[T] { def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) } @@ -79,18 +79,24 @@ object augments { this.height == that.height } -// Generic augments with additional parameters +} + +object augments2 { + import augments.{Eql, eqlToHasEql} + // Nested generic arguments - augment List[List[type U]] { + augment flatLists @ List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } - augment (type T: Eql, T) { + augment samePairs @ (type T: Eql, T) { def isSame = this._1 === this._2 } + } import augments._ +import augments2.{flatLists, samePairs} object Test extends App { val c = Circle(0, 1, 2) println(c.area) @@ -116,5 +122,5 @@ object Test extends App { println(List(List(1), List(2, 3)).flattened.maxx) println(Array(1, 2, 3).maxx) println((2, 3).isSame) - println((3, 3).isSame) + println(samePairs((3, 3)).isSame) } \ No newline at end of file From d4f50df9ccd5180c298dbd748afb3980a867d263 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Feb 2018 10:12:23 +0100 Subject: [PATCH 33/50] Use semantic names for augmentations Also, display anonymous augmentation names in nicer form. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 26 +++++++++---------- .../src/dotty/tools/dotc/core/NameKinds.scala | 1 + .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/printing/RefinedPrinter.scala | 10 ++++++- tests/neg/augment.scala | 7 +++-- tests/run/augment.scala | 1 - 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 753a64e20fe6..f488530e408b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -6,7 +6,7 @@ import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._, transform.SymUtils._ -import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} +import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, AugmentName} import language.higherKinds import typer.FrontEnd import collection.mutable.ListBuffer @@ -778,16 +778,16 @@ object desugar { * * where * + * = , if there is a ` @` binding + * = unqiue, expanded name relative to top-level class of , otherwise + * = "_augment__to_" if is nonempty + * = "_augment_" otherwise + * = underlying type name of , or "" + * = underlying type name of first extended parent, or "" + * * (, ) = decomposeTypePattern() * (, ) = desugarTypeBindings() * = concatenated with in one clause - * = if there is a `id @` binding - * = To_in_$$ where is first extended class name - * - * = Augmentation_in_$$ if no such exists - * = underlying type name of - * = flat name of enclosing toplevel class - * = counter making prefix unique * = with each occurrence of unqualified `this` substituted by `$this`. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -800,7 +800,7 @@ object desugar { * where * * = where each method definition gets as last parameter section. - * , are as above. + * , are as above. */ def augmentation(tree: Augment)(implicit ctx: Context): Tree = { val Augment(id, augmented, impl) = tree @@ -818,11 +818,11 @@ object desugar { def clsName(tree: Tree): String = leadingName("", tree) val fromName = clsName(augmented) val toName = impl.parents match { - case parent :: _ if !clsName(parent).isEmpty => "To" + clsName(parent) - case _ => "Augmentation" + case parent :: _ if !clsName(parent).isEmpty => "_to_" + clsName(parent) + case _ => "" } - val prefix = s"${fromName}${toName}_in_${ctx.owner.topLevelClass.flatName}" - UniqueName.fresh(prefix.toTermName).toTypeName + val core = s"${str.AUGMENT}$fromName$toName".toTermName + AugmentName.fresh(core.expandedName(ctx.owner.topLevelClass)).toTypeName } val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index bf758ac77300..5a72417bf4f6 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -291,6 +291,7 @@ object NameKinds { val SkolemName = new UniqueNameKind("?") val LiftedTreeName = new UniqueNameKind("liftedTree") val SuperArgName = new UniqueNameKind("$superArg$") + val AugmentName = new UniqueNameKind("_") /** A kind of unique extension methods; Unlike other unique names, these can be * unmangled. diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index ea1bf83e2c67..008f22b28d77 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -28,6 +28,7 @@ object StdNames { final val LOCALDUMMY_PREFIX = " return s"import $info.expr.show" case _ => } + sym.name.toTermName match { + case AugmentName(ExpandedName(_, core), n) => + assert(core.startsWith(str.AUGMENT)) + return Str(s"augmentation ${core.drop(str.AUGMENT.length)}") ~ (Str(s"/$n") provided n > 1) + case _ => + } + if (sym.is(ModuleClass)) kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym)) else diff --git a/tests/neg/augment.scala b/tests/neg/augment.scala index 9efc7083ce45..22d918a8f8b6 100644 --- a/tests/neg/augment.scala +++ b/tests/neg/augment.scala @@ -73,6 +73,10 @@ object augments { this.width == that.width && this.height == that.height } + + augment List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } } object augments2 { @@ -108,8 +112,7 @@ object Test extends App { println(r1 === r1) println(r1 === r2) println(List(1, 2, 3).second) - println(List(List(1), List(2, 3)).flattened) - println(List(List(1), List(2, 3)).flattened.maxx) + println(List(List(1), List(2, 3)).flattened) // error: type error + note that implicit conversions are ambiguous println(Array(1, 2, 3).maxx) println((2, 3).isSame) println((3, 3).isSame) diff --git a/tests/run/augment.scala b/tests/run/augment.scala index 73318049d9e8..60bc875c63bd 100644 --- a/tests/run/augment.scala +++ b/tests/run/augment.scala @@ -78,7 +78,6 @@ object augments { this.width == that.width && this.height == that.height } - } object augments2 { From 823ba95e4460138372fc60d5d9e0e102c6979f7c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 28 Feb 2018 11:07:26 +0100 Subject: [PATCH 34/50] Add regression test for idempotency issue on static methods This was fixed in one of the previous commits that affeted names --- tests/pos/augment.scala | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/pos/augment.scala diff --git a/tests/pos/augment.scala b/tests/pos/augment.scala new file mode 100644 index 000000000000..301acda823a9 --- /dev/null +++ b/tests/pos/augment.scala @@ -0,0 +1,95 @@ +import Predef.{any2stringadd => _, _} +object augments { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + augment Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + augment Circle extends HasArea { + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + augment List[type T] { + def second = this.tail.head + } + +// Specific trait implementations + + augment List[Int] { + def maxx = (0 /: this)(_ `max` _) + } + + augment 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 + } + + augment Rectangle[type T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + + augment Rectangle[type T](implicit ev: Eql[T]) { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic augments + + augment (type T) { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic augments + + trait HasEql[T] { + def === (that: T): Boolean + } + + augment eqlToHasEql @ (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment (type T)(implicit ev: Eql[T]) { + def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } +} + +object augments2 { + import augments.{Eql, eqlToHasEql} + // Nested generic arguments + + augment flatLists @ List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + augment samePairs @ (type T: Eql, T) { + def isSame = this._1 === this._2 + } + +} From 90ec0ee544cbe39f71f00ce9362cbc4143ee5556 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Feb 2018 12:25:38 +0100 Subject: [PATCH 35/50] Add docs for extension methods --- .../reference/augments/method-augments.md | 189 ++++++++++++++++++ docs/sidebar.yml | 10 + tests/neg/augment.scala | 4 + tests/pos/typepats.scala | 8 + tests/run/augment.scala | 40 ++++ 5 files changed, 251 insertions(+) create mode 100644 docs/docs/reference/augments/method-augments.md diff --git a/docs/docs/reference/augments/method-augments.md b/docs/docs/reference/augments/method-augments.md new file mode 100644 index 000000000000..5b59a01726d4 --- /dev/null +++ b/docs/docs/reference/augments/method-augments.md @@ -0,0 +1,189 @@ +--- +layout: doc-page +title: "Method Augmentations" +--- + +Method augmentations are a way to define _extension methods_. Here is a simple one: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +augment Circle { + def circumference: Double = this.radius * math.Pi * 2 +} +``` + +The `augment Circle` clause adds an extension 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`. Unlike for regular methods, +an explicit `this` is mandatory to refer to members of the receiver. So the following +gives a compilation error: + +```scala + | def circumference = radius * math.Pi * 2 + | ^^^^^^ + | not found: radius +``` + +### Scope of Augment Clauses + +Augment clauses can appear anywhere in a program; there is no need to co-define them with the types they augment. Extension methods are available whereever their defining augment clause is in scope. Augment clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can also attach a name to an augment clause, like this: + +```scala +package shapeOps + +augment circleOps @ Circle { + def circumference: Double = this.radius * math.Pi * 2 + def area: Double = this.radius * this.radius * math.Pi +} +``` +Labelled augments can be imported individually by their name: + +```scala +import shapeOps.circleOps // makes circumference and area available +``` + +### Augmented Types + +An augment clause may add methods to arbitrary types. For instance, the following +clause adds a `longestStrings` extension method to a `Seq[String]`: + +```scala +augment Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) + } +} +``` + +### Augmented Type Patterns + +The previous example augmented a specific instance of a generic type. It is also possible +to augment a generic type itself, using a _type pattern_: + +```scala +augment List[type T] { + def second: T = this.tail.head +} +``` + +The `type T` argument indicates that the augment applies to `List[T]`s for any type `T`. +We also say that `type T` introduces `T` as a variable in the type pattern `List[type T]`. +Type variables may appear anywhere in a type pattern. Example: + +```scala +augment List[List[type T]] { + def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +### Type Patterns in Cases + +The `type ...` syntax for pattern bound type variables also applies to patterns in +case clauses of `match` and `try` expressions. For instance: + +```scala +def f[T](s: Set[T], x: T) = s match { + case _: SortedSet[type U] => ... // binds `U`, infers that `U = T` + case _ => +} +``` + +Previously, one used a lower-case name to indicate a variable in a type pattern, as in: + +```scala + case _: SortedSet[u] => ... // binds `u`, infers that `u = T` +``` + +While being more regular wrt term variables in patterns, this usage is harder to read, and has the problem that it feels unnatrual to have to write type names in lower case. It will therefore be phased out to be replaced by the explicit `type T` syntax. + +Type patterns in cases only come in unbounded form; the bounds defined in the next section are not applicable to them. + +### Bounds in Augmented Type Patterns + +It is also possible to use bounds for the type variables in an augmented type pattern. Examples: + +```scala +augment List[type T <: Shape] { + def totalArea = this.map(_.area).sum +} +``` + +Context-bounds are also supported: + +```scala +augment Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.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 +augment Seq[type T](implicit ev: math.Ordering[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 augmentation. For instance, the following augment introduces `x ~ y` as an alias +for `(x, y)`: + +```scala +augment (type 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 augments 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) + + augment (type T) { + def ensuring[U](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/sidebar.yml b/docs/sidebar.yml index ef12510e7574..ae81d96700e7 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -29,6 +29,16 @@ sidebar: url: docs/reference/dependent-function-types.html - title: Literal Singleton Types url: docs/reference/singleton-types.html + - title: Augmentations + subsection: + - title: Method Augmentations + url: docs/reference/augments/method-augments.html + - title: Type Patterns + url: docs/reference/augments/type-patterns.html + - title: Trait Augmentations + url: docs/reference/augments/trait-augments.html + - title: Translation of Augmentations + url: docs/reference/augments/translation.html - title: Enums subsection: - title: Enumerations diff --git a/tests/neg/augment.scala b/tests/neg/augment.scala index 22d918a8f8b6..aef5bed34b75 100644 --- a/tests/neg/augment.scala +++ b/tests/neg/augment.scala @@ -10,6 +10,10 @@ object augments { private val p = math.Pi // error: `def` expected } + augment Circle { + def circumference = radius * math.Pi * 2 // error: not found + } + // Trait implementations trait HasArea { diff --git a/tests/pos/typepats.scala b/tests/pos/typepats.scala index df051fdc4842..0b21547d9393 100644 --- a/tests/pos/typepats.scala +++ b/tests/pos/typepats.scala @@ -10,4 +10,12 @@ object Test { case _: C[type T] => val x: T = 3 } + + import collection.immutable.SortedSet + val s: Set[Int] = SortedSet(1, 2, 3) + s match { + case _: SortedSet[type T] => + val x: T = 3 + case _ => + } } \ No newline at end of file diff --git a/tests/run/augment.scala b/tests/run/augment.scala index 60bc875c63bd..2ccc3be902ee 100644 --- a/tests/run/augment.scala +++ b/tests/run/augment.scala @@ -94,6 +94,46 @@ object augments2 { } +object docs { + augment Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) + } + } + + augment List[List[type T]] { + def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) + } + + augment Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 + } + + object PostConditions { + opaque type EnsureResult[T] = T + + private object EnsureResult { + def wrap[T](x: T): EnsureResult[T] = x + def unwrap[T](x: EnsureResult[T]): T = x + } + + def result[T](implicit er: EnsureResult[T]): T = EnsureResult.unwrap(er) + + augment (type T) { + def ensuring[U](f: implicit EnsureResult[T] => Boolean): T = { + assert(f(EnsureResult.wrap(this))) + this + } + } + } + import PostConditions._ + + val s = List(1, 2, 3).sum.ensuring(result == 6) + +} + import augments._ import augments2.{flatLists, samePairs} object Test extends App { From 0b0c2e0cb5472353bf760a22a848405fded90bed Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Feb 2018 18:38:20 +0100 Subject: [PATCH 36/50] More docs for augmentations --- .../reference/augments/method-augments.md | 2 +- .../docs/reference/augments/trait-augments.md | 76 +++++++++++++++ docs/docs/reference/augments/translation.md | 93 +++++++++++++++++++ .../dropped/implicit-value-classes.md | 35 +++++++ docs/sidebar.yml | 4 +- tests/run/augment.scala | 4 +- 6 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 docs/docs/reference/augments/trait-augments.md create mode 100644 docs/docs/reference/augments/translation.md create mode 100644 docs/docs/reference/dropped/implicit-value-classes.md diff --git a/docs/docs/reference/augments/method-augments.md b/docs/docs/reference/augments/method-augments.md index 5b59a01726d4..606e08ff8f0c 100644 --- a/docs/docs/reference/augments/method-augments.md +++ b/docs/docs/reference/augments/method-augments.md @@ -78,7 +78,7 @@ augment List[type T] { ``` The `type T` argument indicates that the augment applies to `List[T]`s for any type `T`. -We also say that `type T` introduces `T` as a variable in the type pattern `List[type T]`. +We also say that `type T` introduces `T` as a _variable_ in the type pattern `List[type T]`. Type variables may appear anywhere in a type pattern. Example: ```scala diff --git a/docs/docs/reference/augments/trait-augments.md b/docs/docs/reference/augments/trait-augments.md new file mode 100644 index 000000000000..64b8cabb6abc --- /dev/null +++ b/docs/docs/reference/augments/trait-augments.md @@ -0,0 +1,76 @@ +--- +layout: doc-page +title: "Trait Augmentations" +--- + +In addition to adding methods, an augmentation can also implement traits and classes. For instance, + +```scala +trait HasArea { + def area: Double +} + +augment circleOps @ Circle extends HasArea { + def area = this.radius * this.radius * math.Pi +} +``` + +This augemntation makes `Circle` implement 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 augmentation above would be like this + +```scala +implicit class circleOps($this: Circle) extends HasArea { + def area = $this.radius * $this.radius * math.Pi +} +``` + +A trait augmentation can thus provide a kind of "extends" relationship that can be defined independently of the types it connects. + +### Generic Trait Augmentations + +Just like method augmentations, trait augmentations can be generic with 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) +} +``` + +The following augment makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: + +```scala +augment (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +### Syntax of Augmentations + +The syntax of augmentations iss pecified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + + Augmentation ::= ‘augment’ [id ‘@’] BindingTypePattern + [[nl] ImplicitParamClause] AugmentClause + AugmentClause ::= ‘extends’ Template + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + + ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’ + ImplicitMods ::= `implicit` [`ghost`] | `ghost` `implicit` + + BindingTypePattern: := AnnotType + Type ::= ... + | ‘type’ TypeParamCore (if inside a BindingTypePattern) + TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds + +In this definition, type patterns and types share the same productions. However, the production + + Type ::= ‘type’ TypeParamCore + +is applicable only inside a `BindingTypePattern`. + + diff --git a/docs/docs/reference/augments/translation.md b/docs/docs/reference/augments/translation.md new file mode 100644 index 000000000000..874edd43926a --- /dev/null +++ b/docs/docs/reference/augments/translation.md @@ -0,0 +1,93 @@ +--- +layout: doc-page +title: "Translation of Augmentations" +--- + +Augmentations are closely related to implicit classes and can be translated into them. In short, +a method augmentation translates into an implicit value class and a trait implementation 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 augmentations 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. + + +### Decomposing Type Patterns + +First, define a function `decompose` to decompose a type pattern `TP` into a list of type binders `tparams` and a type `T`. Going from left to right, every type binder `type U ` in `TP` is added to `tparams` and is replaced by the reference `U` in `T`. For instance, the type pattern + +```scala +Map[type K <: AnyRef, List[type T]] +``` +would be decomposed into the binders `type K <: AnyRef` and `type T` and the type `Map[K, List[T]]`. + +### Translation of Method Augmentations + +Now, a assume a method augmentation + + augment @ { } + +where ` @` or `` can be absent. For simplicity assume that there are no context bounds in +type definitions of ``. This is no essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. Let `(, )` be the decomposition of ``. +The augment clause can be translated to the following implicit value class: + + implicit class ($this: ) extends AnyVal { } + +Here `` results from `` by augmenting any definition in with the parameters and +replacing any occurrence of `this` with `$this`. If the label ` @` is missing, a fresh compiler-generated name is chosen instead as the name of the implicit class. + +For example, the augmentation + +```scala +augment seqOps @ Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class seqOps[T]($this: List[T]) extends AnyVal { + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = $this.zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Trait Augmentations + +Now, assume a trait augmentation + + augment @ extends { } + +Let again `(, )` be the decomposition of ``. This augmentation is translated to + + implicit class ($this: ) extends { } + +As before, `` results from `` by replacing any occurrence of `this` with `$this`. However, all +parameters in now stay on the class definition, instead of beging 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 trait augmentation + +```scala +class Test { + augment (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } +} +``` + +would be translated to + +```scala +class Test { + implicit class Test$$_augment_T_to_HasEql_1 [T] + ($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) + } +} +``` + +where the name `Test$$_augment_T_to_HasEql_1` is compiler generated and implementation dependent. \ No newline at end of file diff --git a/docs/docs/reference/dropped/implicit-value-classes.md b/docs/docs/reference/dropped/implicit-value-classes.md new file mode 100644 index 000000000000..857a83bd23b0 --- /dev/null +++ b/docs/docs/reference/dropped/implicit-value-classes.md @@ -0,0 +1,35 @@ +--- +layout: doc-page +title: Dropped: Implicit Classes and Value Classes +--- + +Scala uses implicit classes to define extension methods and conversions. E.g., from `Predef.scala`: + +```scala +implicit final class ArrowAssoc[A](private val self: A) extends AnyVal { + def -> [B](y: B): (A, B) = (self, y) +} +``` + +Implicit classes and value classes are still supported in Dotty, in order to support cross compilation with Scala 2. But they will be phased out in the future. The `ArrowAssoc` class can be written as an [augmentation](../augments/method-augments.html) as follows: + +```scala +augment (type A) { + def -> [B](that: B): (A, B) = (this, that) +} +``` + +Most other uses of value classes can be expressed by [opaque type aliases](../opaque.html). +There are only two aspects of value classes that are not covered: + + - A user-definable `toString` method. E.g. if `Meter` is a value class implemented in terms of `Double`, it is possible to define `toString` to that `new Meter(10).toString` prints `10m` instead of `10`. + + - Type tests and checked type casts for value classes. E.g. if `x: Any` once can have a pattern match like this: + + x match { case m: Meter => ... } + + The match succeeds if `x` is a `Meter` but not if it is `Double`. By contrast, an implementation in terms of + opaque type aliases would flag the match with an unchecked warning and succeed for both. This is a consequence of using the same runtime representation for `Meter` and `Double`. + +Given the quite high complexity of value classes, in terms of rules what is legal and what is not, and in terms of their boxing model, it is attractive to drop them. The combinaton of opaque types and augmentations is both simpler and generally more pleasant to use. + diff --git a/docs/sidebar.yml b/docs/sidebar.yml index ae81d96700e7..b20c25d1c30a 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -33,8 +33,6 @@ sidebar: subsection: - title: Method Augmentations url: docs/reference/augments/method-augments.html - - title: Type Patterns - url: docs/reference/augments/type-patterns.html - title: Trait Augmentations url: docs/reference/augments/trait-augments.html - title: Translation of Augmentations @@ -113,6 +111,8 @@ sidebar: url: docs/reference/dropped/auto-apply.html - title: Weak Conformance url: docs/reference/dropped/weak-conformance.html + - title: Implicit and Value Classes + url: docs/reference/dropped/implicit-value-classes.html - title: Contributing subsection: - title: Getting Started diff --git a/tests/run/augment.scala b/tests/run/augment.scala index 2ccc3be902ee..f1826ab772a1 100644 --- a/tests/run/augment.scala +++ b/tests/run/augment.scala @@ -19,13 +19,13 @@ object augments { def area = this.radius * this.radius * math.Pi } -// Generic trait implementations +// Generic augmentations augment List[type T] { def second = this.tail.head } -// Specific trait implementations +// Specific augmentations augment List[Int] { def maxx = (0 /: this)(_ `max` _) From 52979961686a45783a9330090861202788ac648d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Mar 2018 18:07:27 +0100 Subject: [PATCH 37/50] Remove duplicate test --- tests/pos/augment.scala | 95 ----------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 tests/pos/augment.scala diff --git a/tests/pos/augment.scala b/tests/pos/augment.scala deleted file mode 100644 index 301acda823a9..000000000000 --- a/tests/pos/augment.scala +++ /dev/null @@ -1,95 +0,0 @@ -import Predef.{any2stringadd => _, _} -object augments { - -// Simple extension methods - - case class Circle(x: Double, y: Double, radius: Double) - - augment Circle { - def circumference = this.radius * math.Pi * 2 - } - -// Trait implementations - - trait HasArea { - def area: Double - } - - augment Circle extends HasArea { - def area = this.radius * this.radius * math.Pi - } - -// Generic trait implementations - - augment List[type T] { - def second = this.tail.head - } - -// Specific trait implementations - - augment List[Int] { - def maxx = (0 /: this)(_ `max` _) - } - - augment 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 - } - - augment Rectangle[type T: Eql] { - def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) - } - - augment Rectangle[type T](implicit ev: Eql[T]) { - def isNotSquare: Boolean = !implicitly[Eql[T]].eql(this.width, this.height) - } - -// Simple generic augments - - augment (type T) { - def ~[U](that: U): (T, U) = (this, that) - } - -// Conditional generic augments - - trait HasEql[T] { - def === (that: T): Boolean - } - - augment eqlToHasEql @ (type T: Eql) extends HasEql[T] { - def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) - } - - augment (type T)(implicit ev: Eql[T]) { - def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) - } - - augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { - def === (that: Rectangle[T]) = - this.x === that.x && - this.y === that.y && - this.width == that.width && - this.height == that.height - } -} - -object augments2 { - import augments.{Eql, eqlToHasEql} - // Nested generic arguments - - augment flatLists @ List[List[type U]] { - def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) - } - - augment samePairs @ (type T: Eql, T) { - def isSame = this._1 === this._2 - } - -} From 1c55e0444792ccbaf05554697b6c0a86f3052e1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 21:27:42 +0100 Subject: [PATCH 38/50] Fix typos --- docs/docs/reference/augments/method-augments.md | 4 ++-- docs/docs/reference/augments/trait-augments.md | 2 +- docs/docs/reference/augments/translation.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/reference/augments/method-augments.md b/docs/docs/reference/augments/method-augments.md index 606e08ff8f0c..a7e128f4d395 100644 --- a/docs/docs/reference/augments/method-augments.md +++ b/docs/docs/reference/augments/method-augments.md @@ -3,7 +3,7 @@ layout: doc-page title: "Method Augmentations" --- -Method augmentations are a way to define _extension methods_. Here is a simple one: +Method augmentation is a way to define _extension methods_. Here is a simple example: ```scala case class Circle(x: Double, y: Double, radius: Double) @@ -36,7 +36,7 @@ gives a compilation error: ### Scope of Augment Clauses -Augment clauses can appear anywhere in a program; there is no need to co-define them with the types they augment. Extension methods are available whereever their defining augment clause is in scope. Augment clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can also attach a name to an augment clause, like this: +Augment clauses can appear anywhere in a program; there is no need to co-define them with the types they augment. Extension methods are available wherever their defining augment clause is in scope. Augment clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can also attach a name to an augment clause, like this: ```scala package shapeOps diff --git a/docs/docs/reference/augments/trait-augments.md b/docs/docs/reference/augments/trait-augments.md index 64b8cabb6abc..fed7cd6a5c50 100644 --- a/docs/docs/reference/augments/trait-augments.md +++ b/docs/docs/reference/augments/trait-augments.md @@ -15,7 +15,7 @@ augment circleOps @ Circle extends HasArea { } ``` -This augemntation makes `Circle` implement the `HasArea` trait. Specifically, it defines an implicit subclass of `HasArea` +This augmentation makes `Circle` implement 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 augmentation above would be like this ```scala diff --git a/docs/docs/reference/augments/translation.md b/docs/docs/reference/augments/translation.md index 874edd43926a..ad5e6b6d9158 100644 --- a/docs/docs/reference/augments/translation.md +++ b/docs/docs/reference/augments/translation.md @@ -66,7 +66,7 @@ Let again `(, )` be the decomposition of ``. This augmentation i implicit class ($this: ) extends { } As before, `` results from `` by replacing any occurrence of `this` with `$this`. However, all -parameters in now stay on the class definition, instead of beging distributed to all members in ``. This is necessary in general, since `` might contain value definitions or other statements that cannot be +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 trait augmentation From 1e4b2a1879fcc49971d2e7baa5ff2d9aa45da19e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Mar 2018 14:56:41 +0100 Subject: [PATCH 39/50] Change syntax from augment/extends to extend/implements Also, drop labels. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 79 ++++++++------ compiler/src/dotty/tools/dotc/ast/untpd.scala | 10 +- .../src/dotty/tools/dotc/core/NameKinds.scala | 1 - .../src/dotty/tools/dotc/core/StdNames.scala | 1 - .../dotty/tools/dotc/parsing/Parsers.scala | 72 ++++++------ .../src/dotty/tools/dotc/parsing/Tokens.scala | 7 +- .../tools/dotc/printing/RefinedPrinter.scala | 8 +- docs/docs/internals/syntax.md | 10 +- .../docs/reference/augments/trait-augments.md | 76 ------------- docs/docs/reference/augments/translation.md | 93 ---------------- .../extend/extension-implementations.md | 76 +++++++++++++ .../extension-methods.md} | 67 ++++++------ docs/docs/reference/extend/translation.md | 103 ++++++++++++++++++ docs/sidebar.yml | 14 +-- tests/neg/{augment.scala => extensions.scala} | 76 +++++++------ tests/pos/opaque-augment.scala | 2 +- tests/pos/t4579.scala | 50 ++++----- tests/run/{augment.scala => extensions.scala} | 59 +++++----- 18 files changed, 406 insertions(+), 398 deletions(-) delete mode 100644 docs/docs/reference/augments/trait-augments.md delete mode 100644 docs/docs/reference/augments/translation.md create mode 100644 docs/docs/reference/extend/extension-implementations.md rename docs/docs/reference/{augments/method-augments.md => extend/extension-methods.md} (67%) create mode 100644 docs/docs/reference/extend/translation.md rename tests/neg/{augment.scala => extensions.scala} (62%) rename tests/run/{augment.scala => extensions.scala} (71%) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index f488530e408b..7e349384998c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -4,9 +4,10 @@ package ast import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ +import util.Chars.isIdentifierPart import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._, transform.SymUtils._ -import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, AugmentName} +import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} import language.higherKinds import typer.FrontEnd import collection.mutable.ListBuffer @@ -771,20 +772,39 @@ object desugar { (elimTypeDefs.transform(tree), bindingsBuf.toList) } - /** augment [ @] extends { } } + private val collectNames = new untpd.UntypedTreeAccumulator[ListBuffer[String]] { + override def apply(buf: ListBuffer[String], tree: Tree)(implicit ctx: Context): ListBuffer[String] = tree match { + case Ident(name) => + buf += name.toString + case Select(qual, name) => + apply(buf, qual) += name.toString + case TypeDef(name, rhs) => + apply(buf += "type" += name.toString, rhs) + case Apply(fn, _) => + apply(buf, fn) + case _ => + foldOver(buf, tree) + } + } + + private def extensionName(ext: Extension)(implicit ctx: Context): TypeName = { + var buf = collectNames(new ListBuffer[String] += "extend", ext.extended) + if (ext.impl.parents.nonEmpty) + buf = collectNames(buf += "implements", ext.impl.parents) + val ids = buf.toList.map(_.filter(isIdentifierPart)).filter(!_.isEmpty) + ids.mkString("_").toTypeName + } + + /** extend implements { } } * -> - * implicit class ($this: ) + * implicit class ($this: ) * extends { } * * where * - * = , if there is a ` @` binding - * = unqiue, expanded name relative to top-level class of , otherwise - * = "_augment__to_" if is nonempty - * = "_augment_" otherwise - * = underlying type name of , or "" - * = underlying type name of first extended parent, or "" - * + * = concatenation of all alphanumeric characters between and including `extend` and `{`, + * mapping every sequence of other characters to a single `_`. + * * (, ) = decomposeTypePattern() * (, ) = desugarTypeBindings() * = concatenated with in one clause @@ -792,38 +812,23 @@ object desugar { * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * - * augment [ @] { } + * extend { } * -> - * implicit class ($this: ) + * implicit class ($this: ) * extends AnyVal { } * * where * * = where each method definition gets as last parameter section. - * , are as above. + * , are as above. */ - def augmentation(tree: Augment)(implicit ctx: Context): Tree = { - val Augment(id, augmented, impl) = tree - val isSimpleExtension = - impl.parents.isEmpty && - impl.self.isEmpty && - impl.body.forall(_.isInstanceOf[DefDef]) - val (decorated, bindings) = decomposeTypePattern(augmented) + def extension(tree: Extension)(implicit ctx: Context): Tree = { + val Extension(extended, impl) = tree + val isSimpleExtension = impl.parents.isEmpty + val (decorated, bindings) = decomposeTypePattern(extended) val (typeParams, evidenceParams) = desugarTypeBindings(bindings, forPrimaryConstructor = !isSimpleExtension) - val decoName = id match { - case Ident(name) => - name.asTypeName - case EmptyTree => - def clsName(tree: Tree): String = leadingName("", tree) - val fromName = clsName(augmented) - val toName = impl.parents match { - case parent :: _ if !clsName(parent).isEmpty => "_to_" + clsName(parent) - case _ => "" - } - val core = s"${str.AUGMENT}$fromName$toName".toTermName - AugmentName.fresh(core.expandedName(ctx.owner.topLevelClass)).toTypeName - } + val extName = extensionName(tree) val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) var constr1 = @@ -841,16 +846,18 @@ object desugar { 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 constr1 = addEvidenceParams(constr1, evidenceParams) val icls = - TypeDef(decoName, + TypeDef(extName, cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) .withFlags(Implicit) - desugr.println(i"desugar $augmented --> $icls") + desugr.println(i"desugar $extended --> $icls") classDef(icls) } @@ -883,7 +890,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) - case tree: Augment => augmentation(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 6bf80f274804..ada6d3e13a96 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,8 +40,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } - /** augment id @ augmented impl */ - case class Augment(id: Tree, augmented: Tree, impl: Template) extends DefTree + /** extend extended impl */ + case class Extension(extended: Tree, impl: Template) extends DefTree case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -414,9 +414,9 @@ 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 Augment(tree: Tree)(id: Tree, augmented: Tree, impl: Template) = tree match { - case tree: Augment if (id eq tree.id) && (augmented eq tree.augmented) && (impl eq tree.impl) => tree - case _ => finalize(tree, untpd.Augment(id, augmented, impl)) + def Extension(tree: Tree)(extended: Tree, impl: Template) = tree match { + case tree: Extension if (extended eq tree.extended) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Extension(extended, impl)) } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 5a72417bf4f6..bf758ac77300 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -291,7 +291,6 @@ object NameKinds { val SkolemName = new UniqueNameKind("?") val LiftedTreeName = new UniqueNameKind("liftedTree") val SuperArgName = new UniqueNameKind("$superArg$") - val AugmentName = new UniqueNameKind("_") /** A kind of unique extension methods; Unlike other unique names, these can be * unmangled. diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 008f22b28d77..ea1bf83e2c67 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -28,7 +28,6 @@ object StdNames { final val LOCALDUMMY_PREFIX = " param()) } } @@ -1935,7 +1935,7 @@ object Parsers { imods = EmptyModifiers paramClause() :: { firstClauseOfCaseClass = false - if (imods.is(Implicit) || ofAugmentation) Nil else clauses() + if (imods.is(Implicit) || ofExtension) Nil else clauses() } } else Nil } @@ -2303,32 +2303,33 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } - /** Augmentation ::= ‘augment’ [id @] BindingTypePattern - * [[nl] ImplicitParamClause] Additions + /** Extension ::= ‘extend’ BindingTypePattern + * [[nl] ImplicitParamClause] ExtensionClause * BindingTypePattern ::= AnnotType - * Additions ::= ‘extends’ Template - * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + * ExtensionClause ::= ‘implements’ Template + * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ */ - def augmentation(): Augment = atPos(in.skipToken(), nameStart) { - var id: Tree = EmptyTree - if (isIdent && lookaheadIn(AT)) { - id = typeIdent() - in.nextToken() - } - val augmented = withinTypePattern(binding = true)(annotType()) - val vparamss = paramClauses(tpnme.EMPTY, ofAugmentation = true).take(1) + def extension(): Extension = atPos(in.skipToken(), nameStart) { + val extended = withinTypePattern(binding = true)(annotType()) + val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) val constr = makeConstructor(Nil, vparamss) - val isSimpleExtension = in.token != EXTENDS - val templ = templateClauseOpt(constr, bodyRequired = true) - if (isSimpleExtension) { - def checkDef(tree: Tree) = tree match { - case _: DefDef | EmptyValDef => // ok - case _ => syntaxError("`def` expected", tree.pos.startPos) + val templ = + if (in.token == IMPLEMENTS) { + in.nextToken() + template(constr, bodyRequired = true)._1 } - checkDef(templ.self) - templ.body.foreach(checkDef) - } - Augment(id, augmented, templ) + else { + if (in.token == EXTENDS) syntaxError("`implements` or `{` expected") + val templ = templateClauseOpt(constr, bodyRequired = true) + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + templ + } + Extension(extended, templ) } /* -------- TEMPLATES ------------------------------------------- */ @@ -2364,7 +2365,10 @@ object Parsers { * TemplateClauseOpt = [TemplateClause] */ def templateClauseOpt(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): Template = - if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum, bodyRequired)._1 } + if (in.token == EXTENDS) { + in.nextToken() + template(constr, isEnum, bodyRequired)._1 + } else { newLineOptWhenFollowedBy(LBRACE) if (in.token == LBRACE || bodyRequired) template(constr, isEnum, bodyRequired)._1 @@ -2443,7 +2447,7 @@ object Parsers { * TemplateStat ::= Import * | Annotations Modifiers Def * | Annotations Modifiers Dcl - * | Augmentation + * | Extension * | Expr1 * | * EnumStat ::= TemplateStat @@ -2474,8 +2478,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() - else if (in.token == AUGMENT) - stats += augmentation() + else if (in.token == EXTEND) + stats += extension() else if (isExprIntro) stats += expr1() else if (isDefIntro(modifierTokensOrCase)) @@ -2520,7 +2524,7 @@ object Parsers { * BlockStat ::= Import * | Annotations [implicit] [lazy] Def * | Annotations LocalModifiers TmplDef - * | Augmentation + * | Extension * | Expr1 * | */ @@ -2531,8 +2535,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() - else if (in.token == AUGMENT) - stats += augmentation() + else if (in.token == EXTEND) + stats += extension() else if (isExprIntro) stats += expr(Location.InBlock) else if (isDefIntro(localModifierTokens)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 426e4ffa8f11..8d03ac9aed11 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -179,7 +179,8 @@ 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 AUGMENT = 66; enter(AUGMENT, "augment") + final val EXTEND = 66; enter(EXTEND, "extend") + final val IMPLEMENTS = 67; enter(IMPLEMENTS, "implements") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -200,7 +201,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, AUGMENT) + final val alphaKeywords = tokenRange(IF, IMPLEMENTS) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -240,7 +241,7 @@ object Tokens extends TokensCommon { /** Is token only legal as start of statement (eof also included)? */ final val mustStartStatTokens = defIntroTokens | modifierTokens | BitSet( - IMPORT, PACKAGE, AUGMENT) + IMPORT, PACKAGE, EXTEND) final val canStartStatTokens = canStartExpressionTokens | mustStartStatTokens | BitSet( AT, CASE) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 18d9a42607a4..a5bceb99dbcc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -8,7 +8,7 @@ import Flags._ import Names._ import Symbols._ import NameOps._ -import NameKinds.{AugmentName, ExpandedName} +import NameKinds.ExpandedName import Constants._ import TypeErasure.ErasedValueType import Contexts.Context @@ -722,12 +722,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case info: ImportType => return s"import $info.expr.show" case _ => } - sym.name.toTermName match { - case AugmentName(ExpandedName(_, core), n) => - assert(core.startsWith(str.AUGMENT)) - return Str(s"augmentation ${core.drop(str.AUGMENT.length)}") ~ (Str(s"/$n") provided n > 1) - case _ => - } if (sym.is(ModuleClass)) kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym)) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 0c3d4b5f66b3..bf09c24413e8 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -212,7 +212,7 @@ Block ::= {BlockStat semi} [BlockResult] BlockStat ::= Import | {Annotation} [‘implicit’ | ‘lazy’] Def | {Annotation} {LocalModifier} TmplDef - | Augmentation + | Extension | Expr1 ForExpr ::= ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’) ForYield(enums, expr) @@ -247,9 +247,9 @@ Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’ -Augmentation ::= ‘augment’ [id ‘@’] BindingTypePattern - [[nl] ImplicitParamClause] AugmentClause Augment(name, templ) -AugmentClause ::= ‘extends’ Template +Extension ::= ‘extend’ BindingTypePattern + [[nl] ImplicitParamClause] ExtensionClause Extension(name, templ) +ExtensionClause ::= ‘implements’ Template | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ BindingTypePattern::= AnnotType ``` @@ -362,7 +362,7 @@ TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} TemplateStat ::= Import | {Annotation [nl]} {Modifier} Def | {Annotation [nl]} {Modifier} Dcl - | Augmentation + | Extension | Expr1 | SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) diff --git a/docs/docs/reference/augments/trait-augments.md b/docs/docs/reference/augments/trait-augments.md deleted file mode 100644 index fed7cd6a5c50..000000000000 --- a/docs/docs/reference/augments/trait-augments.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -layout: doc-page -title: "Trait Augmentations" ---- - -In addition to adding methods, an augmentation can also implement traits and classes. For instance, - -```scala -trait HasArea { - def area: Double -} - -augment circleOps @ Circle extends HasArea { - def area = this.radius * this.radius * math.Pi -} -``` - -This augmentation makes `Circle` implement 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 augmentation above would be like this - -```scala -implicit class circleOps($this: Circle) extends HasArea { - def area = $this.radius * $this.radius * math.Pi -} -``` - -A trait augmentation can thus provide a kind of "extends" relationship that can be defined independently of the types it connects. - -### Generic Trait Augmentations - -Just like method augmentations, trait augmentations can be generic with 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) -} -``` - -The following augment makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: - -```scala -augment (type T: Eql) extends HasEql[T] { - def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) -} -``` - -### Syntax of Augmentations - -The syntax of augmentations iss pecified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) - - Augmentation ::= ‘augment’ [id ‘@’] BindingTypePattern - [[nl] ImplicitParamClause] AugmentClause - AugmentClause ::= ‘extends’ Template - | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ - - ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’ - ImplicitMods ::= `implicit` [`ghost`] | `ghost` `implicit` - - BindingTypePattern: := AnnotType - Type ::= ... - | ‘type’ TypeParamCore (if inside a BindingTypePattern) - TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds - -In this definition, type patterns and types share the same productions. However, the production - - Type ::= ‘type’ TypeParamCore - -is applicable only inside a `BindingTypePattern`. - - diff --git a/docs/docs/reference/augments/translation.md b/docs/docs/reference/augments/translation.md deleted file mode 100644 index ad5e6b6d9158..000000000000 --- a/docs/docs/reference/augments/translation.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -layout: doc-page -title: "Translation of Augmentations" ---- - -Augmentations are closely related to implicit classes and can be translated into them. In short, -a method augmentation translates into an implicit value class and a trait implementation 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 augmentations 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. - - -### Decomposing Type Patterns - -First, define a function `decompose` to decompose a type pattern `TP` into a list of type binders `tparams` and a type `T`. Going from left to right, every type binder `type U ` in `TP` is added to `tparams` and is replaced by the reference `U` in `T`. For instance, the type pattern - -```scala -Map[type K <: AnyRef, List[type T]] -``` -would be decomposed into the binders `type K <: AnyRef` and `type T` and the type `Map[K, List[T]]`. - -### Translation of Method Augmentations - -Now, a assume a method augmentation - - augment @ { } - -where ` @` or `` can be absent. For simplicity assume that there are no context bounds in -type definitions of ``. This is no essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. Let `(, )` be the decomposition of ``. -The augment clause can be translated to the following implicit value class: - - implicit class ($this: ) extends AnyVal { } - -Here `` results from `` by augmenting any definition in with the parameters and -replacing any occurrence of `this` with `$this`. If the label ` @` is missing, a fresh compiler-generated name is chosen instead as the name of the implicit class. - -For example, the augmentation - -```scala -augment seqOps @ Seq[type T: math.Ordering] { - def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 - def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 -} -``` - -would be translated to: - -```scala -implicit class seqOps[T]($this: List[T]) extends AnyVal { - def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 - def indexOfSmallest(implicit $ev: math.Ordering[T]) = $this.zipWithIndex.minBy(_._1)._2 -} -``` - -### Translation of Trait Augmentations - -Now, assume a trait augmentation - - augment @ extends { } - -Let again `(, )` be the decomposition of ``. This augmentation is translated to - - implicit class ($this: ) extends { } - -As before, `` results 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 trait augmentation - -```scala -class Test { - augment (type T: Eql) extends HasEql[T] { - def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) - } -} -``` - -would be translated to - -```scala -class Test { - implicit class Test$$_augment_T_to_HasEql_1 [T] - ($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { - def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) - } -} -``` - -where the name `Test$$_augment_T_to_HasEql_1` is compiler generated and implementation dependent. \ No newline at end of file diff --git a/docs/docs/reference/extend/extension-implementations.md b/docs/docs/reference/extend/extension-implementations.md new file mode 100644 index 000000000000..9801441b1649 --- /dev/null +++ b/docs/docs/reference/extend/extension-implementations.md @@ -0,0 +1,76 @@ +--- +layout: doc-page +title: "Extension Implementations" +--- + +In addition to adding methods, an extension can also implement traits and classes. For instance, + +```scala +trait HasArea { + def area: Double +} + +extend Circle implements HasArea { + def area = this.radius * this.radius * math.Pi +} +``` + +This extension makes `Circle` implement 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 circleOps($this: Circle) extends HasArea { + def area = $this.radius * $this.radius * math.Pi +} +``` + +An extension implementation can thus provide a kind of "implements" relationship that can be defined independently of the types it connects. + +### Adding Implementations to Generic Traits + +Just like extension methods, extension implementations 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) +} +``` + +The following extension makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: + +```scala +extend (type T: Eql) implements 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) + + Extension ::= ‘extend’ BindingTypePattern + [[nl] ImplicitParamClause] ExtensionClause + ExtensionClause ::= ‘implements’ Template + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + + ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’ + ImplicitMods ::= `implicit` [`erased`] | `erased` `implicit` + + BindingTypePattern: := AnnotType + Type ::= ... + | ‘type’ TypeParamCore (if inside a BindingTypePattern) + TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds + +In this definition, type patterns and types share the same productions. However, the production + + Type ::= ‘type’ TypeParamCore + +is applicable only inside a `BindingTypePattern`. + + diff --git a/docs/docs/reference/augments/method-augments.md b/docs/docs/reference/extend/extension-methods.md similarity index 67% rename from docs/docs/reference/augments/method-augments.md rename to docs/docs/reference/extend/extension-methods.md index a7e128f4d395..dc6b8cf6895b 100644 --- a/docs/docs/reference/augments/method-augments.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -1,19 +1,19 @@ --- layout: doc-page -title: "Method Augmentations" +title: "Extension Methods" --- -Method augmentation is a way to define _extension methods_. Here is a simple example: +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) -augment Circle { +extend Circle { def circumference: Double = this.radius * math.Pi * 2 } ``` -The `augment Circle` clause adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: +The `extend Circle` clause adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: ```scala val circle = Circle(0, 0, 1) @@ -34,31 +34,28 @@ gives a compilation error: | not found: radius ``` -### Scope of Augment Clauses +### Scope of Extend Clauses -Augment clauses can appear anywhere in a program; there is no need to co-define them with the types they augment. Extension methods are available wherever their defining augment clause is in scope. Augment clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can also attach a name to an augment clause, like this: +Extend clauses 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 extend clause is in scope. Extend clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can wrap extend clauses in objects, like this: ```scala -package shapeOps - -augment circleOps @ Circle { - def circumference: Double = this.radius * math.Pi * 2 - def area: Double = this.radius * this.radius * math.Pi +object circleOps { + extend Circle { + def circumference: Double = this.radius * math.Pi * 2 + def area: Double = this.radius * this.radius * math.Pi + } } -``` -Labelled augments can be imported individually by their name: - -```scala -import shapeOps.circleOps // makes circumference and area available +... +import circleOps._ ``` -### Augmented Types +### Extended Types -An augment clause may add methods to arbitrary types. For instance, the following +An extension can add methods to arbitrary types. For instance, the following clause adds a `longestStrings` extension method to a `Seq[String]`: ```scala -augment Seq[String] { +extend Seq[String] { def longestStrings: Seq[String] = { val maxLength = this.map(_.length).max this.filter(_.length == maxLength) @@ -66,23 +63,21 @@ augment Seq[String] { } ``` -### Augmented Type Patterns +### Extended Type Patterns -The previous example augmented a specific instance of a generic type. It is also possible -to augment a generic type itself, using a _type pattern_: +The previous example extended a specific instance of a generic type. It is also possible +to extend a generic type itself, using a _type pattern_: ```scala -augment List[type T] { +extend List[type T] { def second: T = this.tail.head } ``` -The `type T` argument indicates that the augment applies to `List[T]`s for any type `T`. -We also say that `type T` introduces `T` as a _variable_ in the type pattern `List[type T]`. -Type variables may appear anywhere in a type pattern. Example: +The `type T` argument indicates that the extension applies to `List[T]`s for any type `T`. We also say that `type T` introduces `T` as a _variable_ in the type pattern `List[type T]`. Type variables may appear anywhere in a type pattern. Example: ```scala -augment List[List[type T]] { +extend List[List[type T]] { def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) } ``` @@ -109,12 +104,12 @@ While being more regular wrt term variables in patterns, this usage is harder to Type patterns in cases only come in unbounded form; the bounds defined in the next section are not applicable to them. -### Bounds in Augmented Type Patterns +### Bounds in Extended Type Patterns -It is also possible to use bounds for the type variables in an augmented type pattern. Examples: +It is also possible to use bounds for the type variables in an extended type pattern. Examples: ```scala -augment List[type T <: Shape] { +extend List[type T <: Shape] { def totalArea = this.map(_.area).sum } ``` @@ -122,7 +117,7 @@ augment List[type T <: Shape] { Context-bounds are also supported: ```scala -augment Seq[type T: math.Ordering] { +extend Seq[type T: math.Ordering] { def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 } @@ -133,7 +128,7 @@ augment Seq[type T: math.Ordering] { 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 -augment Seq[type T](implicit ev: math.Ordering[T]) { +extend Seq[type T](implicit ev: math.Ordering[T]) { def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 } @@ -143,16 +138,16 @@ 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 augmentation. For instance, the following augment introduces `x ~ y` as an alias +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 -augment (type T) { +extend (type 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 augments to provide a zero-overhead abstraction. +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 { @@ -165,7 +160,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) - augment (type T) { + extend (type T) { def ensuring[U](condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(this) assert(condition) diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md new file mode 100644 index 000000000000..3a6090201570 --- /dev/null +++ b/docs/docs/reference/extend/translation.md @@ -0,0 +1,103 @@ +--- +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 extension with an `implements` clause 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. + + +### Decomposing Type Patterns + +First, define a function `decompose` to decompose a type pattern `TP` into a list of type binders `tparams` and a type `T`. Going from left to right, every type binder `type U ` in `TP` is added to `tparams` and is replaced by the reference `U` in `T`. For instance, the type pattern + +```scala +Map[type K <: AnyRef, List[type T]] +``` +would be decomposed into the binders `type K <: AnyRef` and `type T` and the type `Map[K, List[T]]`. + +### Computing an Extension Name + +Extensions are anonymous -- the first identifier given in an extension designates the type that is extended, not the name of the extension itself. The name of an extension class is computed instead in a predictable way from the extended type pattern and the implemented traits as follows: + + 1. Take the sequence of tokens starting with `extends` up to the opening brace of the templete definitions. + 2. Drop an implicit parameter list if one is given and also drop any value arguments to parent constructors. + 3. Drop any tokens that are not keywords of identifiers. + 4. In each token resulting from the previous step, drop all characters that are not legal parts of alphanumeric identifiers. This leaves all characters that satisfy the `java.lang.Character.isUnicodeIdentifierPart` predicate as well as `$`. Among Ascii characters, the retained characters are all letters, digits, as well as `_` and `$`. + 5. Concatenate all non-empty strings resulting from the previous step with `_` separators. + +It is an error if two extensions defined in the same scope have the same computed name. These double definition errors can always be avoided by grouping augments together or, as a last resort, defining suitable type aliases. The scheme gives stable names that do not depend on the order in which definitions are given. + +### Translation of Extension Methods + +Now, a assume an extension + + extend { } + +where `` can be absent. For simplicity assume that there are no context bounds in +type definitions of ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. Let `(, )` be the decomposition of ``. +The extension can be translated to the following implicit value class: + + implicit class ($this: ) extends AnyVal { } + +Here `` is the computed extension name +and `` results from `` by augmenting any definition in with the parameters and +replacing any occurrence of `this` with `$this`. + +For example, the extension + +```scala +extend Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class extend_Seq_type_T_math_Ordering [T]($this: List[T]) extends AnyVal { + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = $this.zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Extension Implementations + +Now, assume an extension + + extend implements { } + +Let again `(, )` be the decomposition of ``. This extension is translated to + + implicit class ($this: ) extends { } + +Again, `` is the computed extension name. +Also 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 +extend (type T: Eql) implements HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +would be translated to + +```scala +implicit class extend_type_T_Eql_implements_HasEql_T [T] + ($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) + } +} +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index b20c25d1c30a..113cfe87760a 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -29,14 +29,14 @@ sidebar: url: docs/reference/dependent-function-types.html - title: Literal Singleton Types url: docs/reference/singleton-types.html - - title: Augmentations + - title: Extend Clauses subsection: - - title: Method Augmentations - url: docs/reference/augments/method-augments.html - - title: Trait Augmentations - url: docs/reference/augments/trait-augments.html - - title: Translation of Augmentations - url: docs/reference/augments/translation.html + - title: Extension Methods + url: docs/reference/extend/extension-methods.html + - title: Extension Implementations + url: docs/reference/extend/extension-implementations.html + - title: Translation of Extensions + url: docs/reference/extend/translation.html - title: Enums subsection: - title: Enumerations diff --git a/tests/neg/augment.scala b/tests/neg/extensions.scala similarity index 62% rename from tests/neg/augment.scala rename to tests/neg/extensions.scala index aef5bed34b75..687e66e899ee 100644 --- a/tests/neg/augment.scala +++ b/tests/neg/extensions.scala @@ -1,16 +1,18 @@ import Predef.{any2stringadd => _, _} -object augments { +object extensions { // Simple extension methods case class Circle(x: Double, y: Double, radius: Double) - augment Circle { + extend Circle { def circumference = this.radius * math.Pi * 2 private val p = math.Pi // error: `def` expected } - augment Circle { + type Circle2 = Circle + + extend Circle2 { def circumference = radius * math.Pi * 2 // error: not found } @@ -20,25 +22,33 @@ object augments { def area: Double } - augment Circle extends HasArea { + abstract class HasAreaClass extends HasArea + + extend Circle implements HasArea { + def area = this.radius * this.radius * math.Pi + } + + extend Circle2 extends HasArea {} // error: `implements` or `{` expected + + extend Circle implements HasAreaClass { def area = this.radius * this.radius * math.Pi } // Generic trait implementations - augment List[type T] { + extend List[type T] { type I = Int // error: `def` expected def second = this.tail.head } // Specific trait implementations - augment List[Int] { self => // error: `def` expected + extend List[Int] { self => // error: `def` expected import java.lang._ // error: `def` expected def maxx = (0 /: this)(_ `max` _) } - augment Array[Int] { + extend Array[Int] { def maxx = (0 /: this)(_ `max` _) } @@ -50,27 +60,27 @@ object augments { def eql (x: T, y: T): Boolean } - augment Rectangle[type T: Eql] { + extend Rectangle[type T: Eql] { def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) } -// Simple generic augments +// Simple generic extensions - augment (type T) { + extend (type T) { def ~[U](that: U): (T, U) = (this, that) } -// Conditional generic augments +// Conditional generic extensions trait HasEql[T] { def === (that: T): Boolean } - augment (type T: Eql) extends HasEql[T] { + extend (type T: Eql) implements HasEql[T] { def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) } - augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { + extend Rectangle[type T: Eql] implements HasEql[Rectangle[T]] { def === (that: Rectangle[T]) = this.x === that.x && this.y === that.y && @@ -78,46 +88,34 @@ object augments { this.height == that.height } - augment List[List[type U]] { + extend List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } } -object augments2 { - import augments.Eql + +object extensions1 { + extend List[List[type T]] { + def flattened: List[T] = (this :\ (Nil: List[T]))(_ ++ _) + } +} + +object extensions2 { + import extensions.Eql // Nested generic arguments - augment flatLists @ List[List[type U]] { + extend List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } - augment samePairs @ (type T: Eql, T) { + extend (type T: Eql, T) { def isSame: Boolean = this._1 === this._2 // error: === is not a member } } -import augments._ -import augments2._ +import extensions1._ +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(r1 === r1) - println(r1 === r2) - println(List(1, 2, 3).second) println(List(List(1), List(2, 3)).flattened) // error: type error + note that implicit conversions are ambiguous - println(Array(1, 2, 3).maxx) - println((2, 3).isSame) - println((3, 3).isSame) } \ No newline at end of file diff --git a/tests/pos/opaque-augment.scala b/tests/pos/opaque-augment.scala index d7d5364b074d..f9eaee4ecd30 100644 --- a/tests/pos/opaque-augment.scala +++ b/tests/pos/opaque-augment.scala @@ -13,7 +13,7 @@ object opaquetypes { // This is the first way to unlift the logarithm type def exponent(l: Logarithm): Double = l - augment Logarithm { + extend 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)) diff --git a/tests/pos/t4579.scala b/tests/pos/t4579.scala index 907593e5bd73..54dff6f47b4a 100644 --- a/tests/pos/t4579.scala +++ b/tests/pos/t4579.scala @@ -111,7 +111,7 @@ object LispCaseClasses extends Lisp { def lookup(n: String): Data = if (n == name) expr(this) else Environment.this.lookup(n); } - def extend(name: String, v: Data) = extendRec(name, (env1 => v)); + def `extend`(name: String, v: Data) = extendRec(name, (env1 => v)); } val EmptyEnvironment = new Environment { def lookup(n: String): Data = lispError("undefined: " + n); @@ -165,7 +165,7 @@ object LispCaseClasses extends Lisp { case CONS(SYM("def"), CONS(SYM(name), CONS(y, CONS(z, NIL())))) => eval(z, env.extendRec(name, (env1 => eval(y, env1)))) case CONS(SYM("val"), CONS(SYM(name), CONS(y, CONS(z, NIL())))) => - eval(z, env.extend(name, eval(y, env))) + eval(z, env.`extend`(name, eval(y, env))) case CONS(SYM("lambda"), CONS(params, CONS(y, NIL()))) => mkLambda(params, y, env) case CONS(SYM("if"), CONS(c, CONS(t, CONS(e, NIL())))) => @@ -194,7 +194,7 @@ object LispCaseClasses extends Lisp { case (List(), List()) => env case (p :: ps1, arg :: args1) => - extendEnv(env.extend(p, arg), ps1, args1) + extendEnv(env.`extend`(p, arg), ps1, args1) case _ => lispError("wrong number of arguments") } @@ -208,26 +208,26 @@ object LispCaseClasses extends Lisp { } val globalEnv = EmptyEnvironment - .extend("=", FUN({ + .`extend`("=", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(if (arg1 == arg2) 1 else 0) case List(STR(arg1),STR(arg2)) => NUM(if (arg1 == arg2) 1 else 0)})) - .extend("+", FUN({ + .`extend`("+", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 + arg2) case List(STR(arg1),STR(arg2)) => STR(arg1 + arg2)})) - .extend("-", FUN({ + .`extend`("-", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 - arg2)})) - .extend("*", FUN({ + .`extend`("*", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 * arg2)})) - .extend("/", FUN({ + .`extend`("/", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 / arg2)})) - .extend("car", FUN({ + .`extend`("car", FUN({ case List(CONS(x, xs)) => x})) - .extend("cdr", FUN({ + .`extend`("cdr", FUN({ case List(CONS(x, xs)) => xs})) - .extend("null?", FUN({ + .`extend`("null?", FUN({ case List(NIL()) => NUM(1) case _ => NUM(0)})) - .extend("cons", FUN({ + .`extend`("cons", FUN({ case List(x, y) => CONS(x, y)})); def evaluate(x: Data): Data = eval(normalize(x), globalEnv); @@ -279,7 +279,7 @@ object LispAny extends Lisp { def lookup(n: String): Data = if (n == name) expr(this) else Environment.this.lookup(n); } - def extend(name: String, v: Data) = extendRec(name, (env1 => v)); + def `extend`(name: String, v: Data) = extendRec(name, (env1 => v)); } val EmptyEnvironment = new Environment { def lookup(n: String): Data = lispError("undefined: " + n); @@ -345,7 +345,7 @@ object LispAny extends Lisp { case 'def :: Symbol(name) :: y :: z :: Nil => eval(z, env.extendRec(name, (env1 => eval(y, env1)))) case 'val :: Symbol(name) :: y :: z :: Nil => - eval(z, env.extend(name, eval(y, env))) + eval(z, env.`extend`(name, eval(y, env))) case 'lambda :: params :: y :: Nil => mkLambda(params, y, env) case 'if :: c :: y :: z :: Nil => @@ -385,7 +385,7 @@ object LispAny extends Lisp { case (List(), List()) => env case (p :: ps1, arg :: args1) => - extendEnv(env.extend(p, arg), ps1, args1) + extendEnv(env.`extend`(p, arg), ps1, args1) case _ => lispError("wrong number of arguments") } @@ -399,25 +399,25 @@ object LispAny extends Lisp { } val globalEnv = EmptyEnvironment - .extend("=", Lambda{ + .`extend`("=", Lambda{ case List(arg1, arg2) => if (arg1 == arg2) 1 else 0}) - .extend("+", Lambda{ + .`extend`("+", Lambda{ case List(arg1: Int, arg2: Int) => arg1 + arg2 case List(arg1: String, arg2: String) => arg1 + arg2}) - .extend("-", Lambda{ + .`extend`("-", Lambda{ case List(arg1: Int, arg2: Int) => arg1 - arg2}) - .extend("*", Lambda{ + .`extend`("*", Lambda{ case List(arg1: Int, arg2: Int) => arg1 * arg2}) - .extend("/", Lambda{ + .`extend`("/", Lambda{ case List(arg1: Int, arg2: Int) => arg1 / arg2}) - .extend("nil", Nil) - .extend("cons", Lambda{ + .`extend`("nil", Nil) + .`extend`("cons", Lambda{ case List(arg1, arg2) => arg1 :: asList(arg2)}) - .extend("car", Lambda{ + .`extend`("car", Lambda{ case List(x :: xs) => x}) - .extend("cdr", Lambda{ + .`extend`("cdr", Lambda{ case List(x :: xs) => xs}) - .extend("null?", Lambda{ + .`extend`("null?", Lambda{ case List(Nil) => 1 case _ => 0}); diff --git a/tests/run/augment.scala b/tests/run/extensions.scala similarity index 71% rename from tests/run/augment.scala rename to tests/run/extensions.scala index f1826ab772a1..202751f33b63 100644 --- a/tests/run/augment.scala +++ b/tests/run/extensions.scala @@ -1,11 +1,11 @@ import Predef.{any2stringadd => _, _} -object augments { +object extensions { // Simple extension methods case class Circle(x: Double, y: Double, radius: Double) - augment Circle { + extend Circle { def circumference = this.radius * math.Pi * 2 } @@ -15,23 +15,23 @@ object augments { def area: Double } - augment Circle extends HasArea { + extend Circle implements HasArea { def area = this.radius * this.radius * math.Pi } -// Generic augmentations +// Generic extendations - augment List[type T] { + extend List[type T] { def second = this.tail.head } -// Specific augmentations +// Specific extendations - augment List[Int] { + extend List[Int] { def maxx = (0 /: this)(_ `max` _) } - augment Array[Int] { + extend Array[Int] { def maxx = (0 /: this)(_ `max` _) } @@ -43,35 +43,35 @@ object augments { def eql (x: T, y: T): Boolean } - augment Rectangle[type T: Eql] { + extend Rectangle[type T: Eql] { def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) } - augment Rectangle[type T](implicit ev: Eql[T]) { + extend Rectangle[type T](implicit ev: Eql[T]) { def isNotSquare: Boolean = !implicitly[Eql[T]].eql(this.width, this.height) } -// Simple generic augments +// Simple generic extensions - augment (type T) { + extend (type T) { def ~[U](that: U): (T, U) = (this, that) } -// Conditional generic augments +// Conditional generic extensions trait HasEql[T] { def === (that: T): Boolean } - augment eqlToHasEql @ (type T: Eql) extends HasEql[T] { - def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + extend (type S: Eql) implements HasEql[S] { + def === (that: S): Boolean = implicitly[Eql[S]].eql(this, that) } - augment (type T)(implicit ev: Eql[T]) { - def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + extend (type T2)(implicit ev: Eql[T2]) { + def ==== (that: T2): Boolean = implicitly[Eql[T2]].eql(this, that) } - augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { + extend Rectangle[type T: Eql] implements HasEql[Rectangle[T]] { def === (that: Rectangle[T]) = this.x === that.x && this.y === that.y && @@ -80,33 +80,34 @@ object augments { } } -object augments2 { - import augments.{Eql, eqlToHasEql} +object extensions2 { + import extensions.{Eql, extend_type_S_Eql_S_implements_HasEql_S} // Nested generic arguments - augment flatLists @ List[List[type U]] { + extend List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } - augment samePairs @ (type T: Eql, T) { + extend (type T: Eql, T) { def isSame = this._1 === this._2 + def isSame2 = extend_type_S_Eql_S_implements_HasEql_S(this._1) == this._2 } } object docs { - augment Seq[String] { + extend Seq[String] { def longestStrings: Seq[String] = { val maxLength = this.map(_.length).max this.filter(_.length == maxLength) } } - augment List[List[type T]] { + extend List[List[type T]] { def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) } - augment Seq[type T: math.Ordering] { + extend Seq[type T: math.Ordering] { def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 } @@ -121,7 +122,7 @@ object docs { def result[T](implicit er: EnsureResult[T]): T = EnsureResult.unwrap(er) - augment (type T) { + extend (type T) { def ensuring[U](f: implicit EnsureResult[T] => Boolean): T = { assert(f(EnsureResult.wrap(this))) this @@ -134,8 +135,8 @@ object docs { } -import augments._ -import augments2.{flatLists, samePairs} +import extensions._ +import extensions2._ object Test extends App { val c = Circle(0, 1, 2) println(c.area) @@ -161,5 +162,5 @@ object Test extends App { println(List(List(1), List(2, 3)).flattened.maxx) println(Array(1, 2, 3).maxx) println((2, 3).isSame) - println(samePairs((3, 3)).isSame) + println(extend_type_T_Eql_T_T((3, 3)).isSame) } \ No newline at end of file From ff65a2bc5dee99f41627f3caa323d2b6b4a12f83 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Mar 2018 16:34:17 +0100 Subject: [PATCH 40/50] Fix test and error reporting --- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/neg/extensions.scala | 25 +------------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d9afe8403c78..a77e1f892197 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2323,7 +2323,7 @@ object Parsers { val templ = templateClauseOpt(constr, bodyRequired = true) def checkDef(tree: Tree) = tree match { case _: DefDef | EmptyValDef => // ok - case _ => syntaxError("`def` expected", tree.pos.startPos) + case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) } checkDef(templ.self) templ.body.foreach(checkDef) diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala index 687e66e899ee..a149e102a77c 100644 --- a/tests/neg/extensions.scala +++ b/tests/neg/extensions.scala @@ -28,7 +28,7 @@ object extensions { def area = this.radius * this.radius * math.Pi } - extend Circle2 extends HasArea {} // error: `implements` or `{` expected + extend Circle2 extends HasArea {} // error: `implements` or `{` expected // error: `def` expected extend Circle implements HasAreaClass { def area = this.radius * this.radius * math.Pi @@ -69,31 +69,8 @@ object extensions { extend (type T) { def ~[U](that: U): (T, U) = (this, that) } - -// Conditional generic extensions - - trait HasEql[T] { - def === (that: T): Boolean - } - - extend (type T: Eql) implements HasEql[T] { - def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) - } - - extend Rectangle[type T: Eql] implements HasEql[Rectangle[T]] { - def === (that: Rectangle[T]) = - this.x === that.x && - this.y === that.y && - this.width == that.width && - this.height == that.height - } - - extend List[List[type U]] { - def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) - } } - object extensions1 { extend List[List[type T]] { def flattened: List[T] = (this :\ (Nil: List[T]))(_ ++ _) From 53298ce1650b5ca8a36d28818c2801b7dab56d25 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Mar 2018 18:09:32 +0100 Subject: [PATCH 41/50] Require implemented types to be traits --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 5 ++++- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 ++ compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 ++- tests/neg/extensions.scala | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 7e349384998c..37ce86f5456f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -853,10 +853,13 @@ object desugar { else constr1 = addEvidenceParams(constr1, evidenceParams) + val mods = + if (isSimpleExtension) EmptyModifiers + else EmptyModifiers.withAddedMod(Mod.TraitImplementation()) val icls = TypeDef(extName, cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) - .withFlags(Implicit) + .withMods(mods.withFlags(Implicit)) desugr.println(i"desugar $extended --> $icls") classDef(icls) } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index ada6d3e13a96..bb574580bed7 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -142,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 TraitImplementation() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index dae5f9b61f49..b4768a9d4464 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -944,7 +944,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.TraitImplementation], + stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { case Select(qual: Super, _) if ctx.scala2Mode => diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala index a149e102a77c..cda8afd90fbb 100644 --- a/tests/neg/extensions.scala +++ b/tests/neg/extensions.scala @@ -30,7 +30,7 @@ object extensions { extend Circle2 extends HasArea {} // error: `implements` or `{` expected // error: `def` expected - extend Circle implements HasAreaClass { + extend Circle implements HasAreaClass { // error: class HasAreaClass is not a trait def area = this.radius * this.radius * math.Pi } From 19d12d9d2d0b6251e405c26c63cf46f09a4e724f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 8 Mar 2018 18:09:48 +0100 Subject: [PATCH 42/50] Another test - magnet pattern --- tests/run/magnet.scala | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/run/magnet.scala diff --git a/tests/run/magnet.scala b/tests/run/magnet.scala new file mode 100644 index 000000000000..7cef6b7aac99 --- /dev/null +++ b/tests/run/magnet.scala @@ -0,0 +1,46 @@ +import scala.concurrent.Future +object magnets { + + enum StatusCode { + case OK, Error + } + + trait Marshaller[T] { def marshall(t: T): String } + class HttpResponse + + sealed trait CompletionMagnet { + type Result + def apply(): Result + } + + object CompletionMagnet { + + extend (StatusCode, type T: Marshaller) implements CompletionMagnet { + type Result = String + def apply(): String = implicitly[Marshaller[T]].marshall(this._2) + } + + extend Future[HttpResponse] implements CompletionMagnet { + type Result = Int + def apply(): Int = 1 + } + + extend Future[StatusCode] implements CompletionMagnet { + type Result = Int + def apply(): Int = 2 + } + } + + implicit object stringMarshaller extends Marshaller[String] { + def marshall(x: String): String = x + } + + def complete(magnet: CompletionMagnet): magnet.Result = magnet() +} + +object Test extends App { + import magnets._ + + assert(complete((StatusCode.OK, "hello")) == "hello") + +} \ No newline at end of file From f8bd21ae5f1d56ddf6dde7c10e5380bfa83102ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 09:59:02 +0100 Subject: [PATCH 43/50] Change syntax of instance declarations - Use `:` instead of `implements`. Still todo: Make the `:` context constraint abstract over such instance declarations. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 19 ++++++++++-------- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 6 +++--- .../src/dotty/tools/dotc/parsing/Tokens.scala | 3 +-- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- ...mentations.md => instance-declarations.md} | 20 ++++++++++--------- docs/docs/reference/extend/translation.md | 14 +++++++------ docs/sidebar.yml | 4 ++-- tests/neg/extensions.scala | 6 +++--- tests/run/extensions.scala | 12 +++++------ 10 files changed, 47 insertions(+), 41 deletions(-) rename docs/docs/reference/extend/{extension-implementations.md => instance-declarations.md} (74%) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 37ce86f5456f..7de9ac97e27d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -773,13 +773,17 @@ object desugar { } private val collectNames = new untpd.UntypedTreeAccumulator[ListBuffer[String]] { + def add(buf: ListBuffer[String], name: Name): ListBuffer[String] = { + val alpha = name.toString.filter(isIdentifierPart) + if (alpha.isEmpty) buf else buf += alpha + } override def apply(buf: ListBuffer[String], tree: Tree)(implicit ctx: Context): ListBuffer[String] = tree match { case Ident(name) => - buf += name.toString + add(buf, name) case Select(qual, name) => - apply(buf, qual) += name.toString + add(apply(buf, qual), name) case TypeDef(name, rhs) => - apply(buf += "type" += name.toString, rhs) + apply(add(buf += "type", name), rhs) case Apply(fn, _) => apply(buf, fn) case _ => @@ -790,12 +794,11 @@ object desugar { private def extensionName(ext: Extension)(implicit ctx: Context): TypeName = { var buf = collectNames(new ListBuffer[String] += "extend", ext.extended) if (ext.impl.parents.nonEmpty) - buf = collectNames(buf += "implements", ext.impl.parents) - val ids = buf.toList.map(_.filter(isIdentifierPart)).filter(!_.isEmpty) - ids.mkString("_").toTypeName + buf = collectNames(buf += "", ext.impl.parents) + buf.toList.mkString("_").toTypeName } - /** extend implements { } } + /** extend : { } } * -> * implicit class ($this: ) * extends { } @@ -855,7 +858,7 @@ object desugar { val mods = if (isSimpleExtension) EmptyModifiers - else EmptyModifiers.withAddedMod(Mod.TraitImplementation()) + else EmptyModifiers.withAddedMod(Mod.InstanceDecl()) val icls = TypeDef(extName, cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index bb574580bed7..471e3184cbee 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -143,7 +143,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class EnumCase() extends Mod(Flags.EmptyFlags) - case class TraitImplementation() 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 a77e1f892197..24726a62e964 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2306,7 +2306,7 @@ object Parsers { /** Extension ::= ‘extend’ BindingTypePattern * [[nl] ImplicitParamClause] ExtensionClause * BindingTypePattern ::= AnnotType - * ExtensionClause ::= ‘implements’ Template + * ExtensionClause ::= : Template * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ */ def extension(): Extension = atPos(in.skipToken(), nameStart) { @@ -2314,12 +2314,12 @@ object Parsers { val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) val constr = makeConstructor(Nil, vparamss) val templ = - if (in.token == IMPLEMENTS) { + if (in.token == COLON) { in.nextToken() template(constr, bodyRequired = true)._1 } else { - if (in.token == EXTENDS) syntaxError("`implements` or `{` expected") + if (in.token == EXTENDS) syntaxError("`:` or `{` expected") val templ = templateClauseOpt(constr, bodyRequired = true) def checkDef(tree: Tree) = tree match { case _: DefDef | EmptyValDef => // ok diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 8d03ac9aed11..1f5d74127ea6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -180,7 +180,6 @@ object Tokens extends TokensCommon { final val ERASED = 64; enter(ERASED, "erased") final val OPAQUE = 65; enter(OPAQUE, "opaque") final val EXTEND = 66; enter(EXTEND, "extend") - final val IMPLEMENTS = 67; enter(IMPLEMENTS, "implements") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -201,7 +200,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, IMPLEMENTS) + final val alphaKeywords = tokenRange(IF, EXTEND) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index b4768a9d4464..1adc47548004 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -944,7 +944,7 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.pos, - traitReq = (parent `ne` parents.head) || original.mods.hasMod[Mod.TraitImplementation], + traitReq = (parent `ne` parents.head) || original.mods.hasMod[Mod.InstanceDecl], stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { diff --git a/docs/docs/reference/extend/extension-implementations.md b/docs/docs/reference/extend/instance-declarations.md similarity index 74% rename from docs/docs/reference/extend/extension-implementations.md rename to docs/docs/reference/extend/instance-declarations.md index 9801441b1649..ffe7901c1333 100644 --- a/docs/docs/reference/extend/extension-implementations.md +++ b/docs/docs/reference/extend/instance-declarations.md @@ -1,21 +1,21 @@ --- layout: doc-page -title: "Extension Implementations" +title: "Instance Declarations" --- -In addition to adding methods, an extension can also implement traits and classes. For instance, +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 } -extend Circle implements HasArea { +extend Circle : HasArea { def area = this.radius * this.radius * math.Pi } ``` -This extension makes `Circle` implement the `HasArea` trait. Specifically, it defines an implicit subclass of `HasArea` +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 @@ -24,11 +24,11 @@ implicit class circleOps($this: Circle) extends HasArea { } ``` -An extension implementation can thus provide a kind of "implements" relationship that can be defined independently of the types it connects. +An instance definition can thus provide a kind of "implements" relationship that can be defined independently of the types it connects. -### Adding Implementations to Generic Traits +### Generic Instance Declarations -Just like extension methods, extension implementations can also be generic and their type parameters can have bounds. +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: @@ -45,18 +45,20 @@ trait HasEql[T] { The following extension makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: ```scala -extend (type T: Eql) implements HasEql[T] { +extend (type T : Eql) : 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) Extension ::= ‘extend’ BindingTypePattern [[nl] ImplicitParamClause] ExtensionClause - ExtensionClause ::= ‘implements’ Template + ExtensionClause ::= ‘:’ Template | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’ diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md index 3a6090201570..24ddab52ea7d 100644 --- a/docs/docs/reference/extend/translation.md +++ b/docs/docs/reference/extend/translation.md @@ -5,7 +5,7 @@ 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 extension with an `implements` clause translates +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) @@ -31,7 +31,9 @@ Extensions are anonymous -- the first identifier given in an extension designate 2. Drop an implicit parameter list if one is given and also drop any value arguments to parent constructors. 3. Drop any tokens that are not keywords of identifiers. 4. In each token resulting from the previous step, drop all characters that are not legal parts of alphanumeric identifiers. This leaves all characters that satisfy the `java.lang.Character.isUnicodeIdentifierPart` predicate as well as `$`. Among Ascii characters, the retained characters are all letters, digits, as well as `_` and `$`. - 5. Concatenate all non-empty strings resulting from the previous step with `_` separators. + 5. Concatenate all non-empty strings resulting from the previous step with `_` separators, + except use `__` (i.e. two underscores) between identifiers coming from the type pattern + and identifiers coming from the implemented traits (if there are any). It is an error if two extensions defined in the same scope have the same computed name. These double definition errors can always be avoided by grouping augments together or, as a last resort, defining suitable type aliases. The scheme gives stable names that do not depend on the order in which definitions are given. @@ -69,11 +71,11 @@ implicit class extend_Seq_type_T_math_Ordering [T]($this: List[T]) extends AnyVa } ``` -### Translation of Extension Implementations +### Translation of Instance Declarations Now, assume an extension - extend implements { } + extend : { } Let again `(, )` be the decomposition of ``. This extension is translated to @@ -87,7 +89,7 @@ parameterized. For example, the extension ```scala -extend (type T: Eql) implements HasEql[T] { +extend (type T: Eql) : HasEql[T] { def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) } ``` @@ -95,7 +97,7 @@ extend (type T: Eql) implements HasEql[T] { would be translated to ```scala -implicit class extend_type_T_Eql_implements_HasEql_T [T] +implicit class extend_type_T_Eql__HasEql_T [T] ($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) } diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 113cfe87760a..3d00df75d185 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -33,8 +33,8 @@ sidebar: subsection: - title: Extension Methods url: docs/reference/extend/extension-methods.html - - title: Extension Implementations - url: docs/reference/extend/extension-implementations.html + - title: Instance Declarations + url: docs/reference/extend/instance-declarations.html - title: Translation of Extensions url: docs/reference/extend/translation.html - title: Enums diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala index cda8afd90fbb..fae4e46d6d78 100644 --- a/tests/neg/extensions.scala +++ b/tests/neg/extensions.scala @@ -24,13 +24,13 @@ object extensions { abstract class HasAreaClass extends HasArea - extend Circle implements HasArea { + extend Circle : HasArea { def area = this.radius * this.radius * math.Pi } - extend Circle2 extends HasArea {} // error: `implements` or `{` expected // error: `def` expected + extend Circle2 extends HasArea {} // error: `:` or `{` expected // error: `def` expected - extend Circle implements HasAreaClass { // error: class HasAreaClass is not a trait + extend Circle : HasAreaClass { // error: class HasAreaClass is not a trait def area = this.radius * this.radius * math.Pi } diff --git a/tests/run/extensions.scala b/tests/run/extensions.scala index 202751f33b63..39823b646264 100644 --- a/tests/run/extensions.scala +++ b/tests/run/extensions.scala @@ -15,7 +15,7 @@ object extensions { def area: Double } - extend Circle implements HasArea { + extend Circle : HasArea { def area = this.radius * this.radius * math.Pi } @@ -63,7 +63,7 @@ object extensions { def === (that: T): Boolean } - extend (type S: Eql) implements HasEql[S] { + extend (type S : Eql) : HasEql[S] { def === (that: S): Boolean = implicitly[Eql[S]].eql(this, that) } @@ -71,7 +71,7 @@ object extensions { def ==== (that: T2): Boolean = implicitly[Eql[T2]].eql(this, that) } - extend Rectangle[type T: Eql] implements HasEql[Rectangle[T]] { + extend Rectangle[type T : Eql] : HasEql[Rectangle[T]] { def === (that: Rectangle[T]) = this.x === that.x && this.y === that.y && @@ -81,16 +81,16 @@ object extensions { } object extensions2 { - import extensions.{Eql, extend_type_S_Eql_S_implements_HasEql_S} + import extensions.{Eql, extend_type_S_Eql_S__HasEql_S} // Nested generic arguments extend List[List[type U]] { def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) } - extend (type T: Eql, T) { + extend (type T : Eql, T) { def isSame = this._1 === this._2 - def isSame2 = extend_type_S_Eql_S_implements_HasEql_S(this._1) == this._2 + def isSame2 = extend_type_S_Eql_S__HasEql_S(this._1) == this._2 } } From b757ba9c93351933b5104924662a48962a755978 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 10:07:56 +0100 Subject: [PATCH 44/50] A blue sky sketch how one could possibly evolve extends clauses --- tests/pending/pos/blueSkyExtensions.scala | 122 ++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tests/pending/pos/blueSkyExtensions.scala diff --git a/tests/pending/pos/blueSkyExtensions.scala b/tests/pending/pos/blueSkyExtensions.scala new file mode 100644 index 000000000000..7eee449db6d2 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions.scala @@ -0,0 +1,122 @@ +/** 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 + } + + extend Int : Monoid { + def + (that: Int) = this + that + static def unit = 0 + } + + extend String : Monoid { + def + (that: Int) = this ++ that + static def unit = "" + } + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extend List[type T : Ord] : 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] + } + + trait Monad[A] extends Functor[A] { + static def pure[A]: This[A] + + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extend List[type 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 + } + } + + extend (type T[X]: Monad[X])[T[type A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + def filter(p: A => Boolean): This[A] + static def empty: This[A] + static def apply(xs: A*): 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] + } + + extend String : MonoIterable[Char] { + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + static def empty = "" + static def apply(xs: A*) = xs.mkString + } + + extend String : Iterable[Char] { + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extend List[type T] : Iterable[T] { + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } + +} \ No newline at end of file From 23c863eb439fba6c548240f6a75ae7dbed523672 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 13:09:30 +0100 Subject: [PATCH 45/50] Fix test --- tests/pending/pos/blueSkyExtensions.scala | 5 +++++ tests/run/magnet.scala | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/pending/pos/blueSkyExtensions.scala b/tests/pending/pos/blueSkyExtensions.scala index 7eee449db6d2..0c0e1690531d 100644 --- a/tests/pending/pos/blueSkyExtensions.scala +++ b/tests/pending/pos/blueSkyExtensions.scala @@ -67,6 +67,11 @@ object blueSkyExtensions { 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.static[F].pure[A] + else develop[A, F](n - 1, f).map(f) + trait Monad[A] extends Functor[A] { static def pure[A]: This[A] diff --git a/tests/run/magnet.scala b/tests/run/magnet.scala index 7cef6b7aac99..f8ba44cb23c2 100644 --- a/tests/run/magnet.scala +++ b/tests/run/magnet.scala @@ -15,17 +15,17 @@ object magnets { object CompletionMagnet { - extend (StatusCode, type T: Marshaller) implements CompletionMagnet { + extend (StatusCode, type T: Marshaller) : CompletionMagnet { type Result = String def apply(): String = implicitly[Marshaller[T]].marshall(this._2) } - extend Future[HttpResponse] implements CompletionMagnet { + extend Future[HttpResponse] : CompletionMagnet { type Result = Int def apply(): Int = 1 } - extend Future[StatusCode] implements CompletionMagnet { + extend Future[StatusCode] : CompletionMagnet { type Result = Int def apply(): Int = 2 } From 420a79d31f35ec9b0fa7d8e5e3c2c532566bc836 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 14:49:22 +0100 Subject: [PATCH 46/50] Fix typos --- docs/docs/reference/extend/extension-methods.md | 2 +- docs/docs/reference/extend/instance-declarations.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index dc6b8cf6895b..77ab8459ac6a 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -161,7 +161,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) extend (type T) { - def ensuring[U](condition: implicit WrappedResult[T] => Boolean): T = { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(this) assert(condition) this diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md index ffe7901c1333..17d6f30564f6 100644 --- a/docs/docs/reference/extend/instance-declarations.md +++ b/docs/docs/reference/extend/instance-declarations.md @@ -38,7 +38,7 @@ trait Eql[T] { } trait HasEql[T] { - def === (that: T) + def === (that: T): Boolean } ``` From 09aa5d2c3d14d9d2159873b0f4ff2d1735b3e5e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Mar 2018 12:13:53 +0100 Subject: [PATCH 47/50] More blue sky variants --- tests/pending/pos/blueSkyExtensions.scala | 44 ++-- tests/pending/pos/blueSkyExtensions2.scala | 196 ++++++++++++++++++ tests/pending/pos/blueSkyExtensions3.scala | 160 ++++++++++++++ tests/pending/pos/blueSkyImplentations2.scala | 66 ++++++ 4 files changed, 451 insertions(+), 15 deletions(-) create mode 100644 tests/pending/pos/blueSkyExtensions2.scala create mode 100644 tests/pending/pos/blueSkyExtensions3.scala create mode 100644 tests/pending/pos/blueSkyImplentations2.scala diff --git a/tests/pending/pos/blueSkyExtensions.scala b/tests/pending/pos/blueSkyExtensions.scala index 0c0e1690531d..f6ca55f0c84b 100644 --- a/tests/pending/pos/blueSkyExtensions.scala +++ b/tests/pending/pos/blueSkyExtensions.scala @@ -31,13 +31,15 @@ object blueSkyExtensions { } extend Int : Monoid { - def + (that: Int) = this + that static def unit = 0 + + def + (that: Int) = this + that } extend String : Monoid { - def + (that: Int) = this ++ that static def unit = "" + + def + (that: Int) = this ++ that } // Ord @@ -62,24 +64,26 @@ object blueSkyExtensions { // Functor and Monad trait Functor[A] { - static def pure[A]: This[A] + static type ThisC[A] <: Functor[A] + static def pure[A]: ThisC[A] - def map[B](f: A => B): This[B] + def map[B](f: A => B): ThisC[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.static[F].pure[A] + if (n == 0) Functor.statics[F].pure[A] else develop[A, F](n - 1, f).map(f) trait Monad[A] extends Functor[A] { - static def pure[A]: This[A] - - def flatMap[B](f: A => This[B]): This[B] + static type ThisC[A] <: Monad[A] + + def flatMap[B](f: A => ThisC[B]): ThisC[B] def map[B](f: A => B) = this.flatMap(f.andThen(pure)) } extend List[type T] : Monad[T] { + static type ThisC[A] = List[A] static def pure[A] = Nil def flatMap[B](f: A => List[B]): List[B] = this match { @@ -95,33 +99,43 @@ object blueSkyExtensions { // Iterables trait MonoIterable[A] { - def filter(p: A => Boolean): This[A] + static type ThisC[A] <: 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] + static type ThisC[A] <: Iterable[A] + + def map[B](f: A => B): ThisC[B] + def flatMap[B](f: A => ThisC[B]): ThisC[B] } extend String : MonoIterable[Char] { - def filter(p: Char => Boolean): String = ... - def map(f: Char => Char): String = ... + static type ThisC[A] = String static def empty = "" static def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... } extend String : Iterable[Char] { + static type ThisC[A] = IndexedSeq[A] + def map[B](f: Char => B): IndexedSeq[B] = ... def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... } extend List[type T] : Iterable[T] { + static type ThisC[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] = ... } - } \ No newline at end of file diff --git a/tests/pending/pos/blueSkyExtensions2.scala b/tests/pending/pos/blueSkyExtensions2.scala new file mode 100644 index 000000000000..bc69a3109d38 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions2.scala @@ -0,0 +1,196 @@ +/** 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 `object` 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 add (that: This): This + } + + trait Monoid extends SemiGroup { + static def unit: This + } + + extend Int : Monoid { + def add (that: Int) = this + that + static def unit = 0 + } + + extend String : Monoid { + def add (that: Int) = this ++ that + static def unit = "" + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// ---> + { + trait TypeClass { + type This + type Static[This] + } + + trait Implementation[From, To <: TypeClass] { + type This = From + def statics: To # Static[From] + def inject(x: From): To { type This = From } + } + + trait SemiGroup extends TypeClass { + def + (that: This): This + } + + trait Monoid extends SemiGroup { + class Static[This] { def unit: This } + } + + implicit object extend_Int_Monoid extends Monoid#Static[Int] with Implementation[Int, Monoid] { + def unit: Int = 0 + def inject($this: Int) = new Monoid { + type This = Int + def + (that: This): This = $this + that + } + } + + implicit object extend_String_Monoid extends Monoid#Static[String] with Implementation[String, Monoid] { + def unit = "" + def inject($this: String): Monoid { type This = String } = + new Monoid { + type This = String + def + (that: This): This = $this + that + } + } + + def impl[From, To](implicit ev: Implementation[From, To]): Implementation[From, To] = + ev + + def sum[T](xs: List[T])(implicit $ev: Implementation[T, Monoid]) = { + //val ev = impl[T, Monoid] + ($ev.statics.unit /: xs)((x, y) => $ev.inject(x) + y) + } + } + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extend List[type T : Ord] : 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] { + object type ThisC[A] <: Functor[A] + object def pure[A]: ThisC[A] + + def map[B](f: A => B): ThisC[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.objects[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + object type ThisC[A] <: Monad[A] + + def flatMap[B](f: A => ThisC[B]): ThisC[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extend List[type T] : Monad[T] { + object type ThisC[A] = List[A] + object 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 + } + } + + extend (type T[X]: Monad[X])[T[type A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + object type ThisC[A] <: MonoIterable[A] + object def empty: This[A] + object def apply(xs: A*): This[A] + + def filter(p: A => Boolean): This[A] + } + + trait Iterable[A] extends MonoIterable[A] { + object type ThisC[A] <: Iterable[A] + + def map[B](f: A => B): ThisC[B] + def flatMap[B](f: A => ThisC[B]): ThisC[B] + } + + extend String : MonoIterable[Char] { + object type ThisC[A] = String + object def empty = "" + object def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + + extend String : Iterable[Char] { + object type ThisC[A] = IndexedSeq[A] + + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extend List[type T] : Iterable[T] { + object type ThisC[A] = List[A] + object def empty = Nil + object 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] = ... + } + + class Foo { + + } + object Foo { + + } +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyExtensions3.scala b/tests/pending/pos/blueSkyExtensions3.scala new file mode 100644 index 000000000000..3870f785baec --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions3.scala @@ -0,0 +1,160 @@ +/** 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 + object { + def unit: This + } + + extend Int : Monoid { + def + (that: Int) = this + that + } + object { + static def unit = 0 + } + + class C { + static Foo + static Bar + } + object Foo extends FooStatic BarStatic BazStatic + + extend String : Monoid { + def + (that: Int) = this ++ that + } + object { + def unit = "" + } + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extend List[type T : Ord] : 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] { + def map[B](f: A => B): ThisC[B] + } + object { + type ThisC[A] <: Functor[A] + def pure[A]: ThisC[A] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.companion[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => ThisC[B]): ThisC[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + object { + type ThisC[A] <: Monad[A] + def pure[A]: ThisC[A] + } + + extend List[type 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 + } + } + object { + type ThisC[A] = List[A] + def pure[A] = Nil + } + + extend (type T[X]: Monad[X])[T[type A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + def filter(p: A => Boolean): ThisC[A] + } + object { + type ThisC[A] <: MonoIterable[A] + def empty: ThisC[A] + def apply(xs: A*): ThisC[A] + } + + trait Iterable[A] extends MonoIterable[A] { + def map[B](f: A => B): ThisC[B] + def flatMap[B](f: A => ThisC[B]): ThisC[B] + } + object { + type ThisC[A] <: Iterable[A] + } + + extend String : MonoIterable[Char] { + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + object { + type ThisC[A] = String + def empty = "" + def apply(xs: A*) = xs.mkString + } + + extend String : Iterable[Char] { + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + object { + type ThisC[A] = IndexedSeq[A] + } + + extend List[type T] : Iterable[T] { + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } + object { + type ThisC[A] = List[A] + def empty = Nil + def apply(xs: A*) = (xs /: Nil)(_ :: _) + } + +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyImplentations2.scala b/tests/pending/pos/blueSkyImplentations2.scala new file mode 100644 index 000000000000..de10c57db637 --- /dev/null +++ b/tests/pending/pos/blueSkyImplentations2.scala @@ -0,0 +1,66 @@ +object runtime { + + trait TypeClass { + type This + type StaticPart[This] + } + + trait Implementation[From] { + type This = From + type Implemented <: TypeClass + def inject(x: From): Implemented { type This = From } + } + + class CompanionOf[T] { type StaticPart[_] } + + def instance[From, To <: TypeClass]( + implicit ev1: Implementation[From] { type Implemented = To }, + ev2: CompanionOf[To]): Implementation[From] { type Implemented = To } & ev2.StaticPart[From] = + ev1.asInstanceOf + + implicit def inject[From](x: From)( + implicit ev1: Implementation[From]): ev1.Implemented { type This = From } = + ev1.inject(x) +} + +object semiGroups { + import runtime._ + + trait SemiGroup extends TypeClass { + def add (that: This): This + } + + trait Monoid extends SemiGroup { + type StaticPart[This] <: MonoidStatic[This] + } + abstract class MonoidStatic[This] { def unit: This } + + implicit def companionOfMonoid: CompanionOf[Monoid] { + type StaticPart[X] = MonoidStatic[X] + } = new CompanionOf[Monoid] { + type StaticPart[X] = MonoidStatic[X] + } + + implicit object extend_Int_Monoid extends MonoidStatic[Int] with Implementation[Int] { + type Implemented = Monoid + def unit: Int = 0 + def inject($this: Int) = new Monoid { + type This = Int + def add (that: This): This = $this + that + } + } + + implicit object extend_String_Monoid extends MonoidStatic[String] with Implementation[String] { + type Implemented = Monoid + def unit = "" + def inject($this: String): Monoid { type This = String } = + new Monoid { + type This = String + def add (that: This): This = $this ++ that + } + } + + def sum[T](xs: List[T])(implicit $ev: Implementation[T] { type Implemented = Monoid } ) = { + (instance[T, Monoid].unit /: xs)((x, y) => (x) add y) + } +} From 51bff8d1fadd310acce613e107066b1bcb4c542d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 09:51:36 +0100 Subject: [PATCH 48/50] Some small updates and variants to blySkyExtensions --- tests/pending/pos/blueSkyExtensions.scala | 29 ++--- tests/pending/pos/blueSkyExtensions1.scala | 134 +++++++++++++++++++++ tests/pending/pos/blueSkyExtensions2.scala | 11 +- 3 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 tests/pending/pos/blueSkyExtensions1.scala diff --git a/tests/pending/pos/blueSkyExtensions.scala b/tests/pending/pos/blueSkyExtensions.scala index f6ca55f0c84b..fd79a92d599a 100644 --- a/tests/pending/pos/blueSkyExtensions.scala +++ b/tests/pending/pos/blueSkyExtensions.scala @@ -32,16 +32,17 @@ object blueSkyExtensions { extend Int : Monoid { static def unit = 0 - def + (that: Int) = this + that } extend 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 { @@ -64,10 +65,8 @@ object blueSkyExtensions { // Functor and Monad trait Functor[A] { - static type ThisC[A] <: Functor[A] - static def pure[A]: ThisC[A] - - def map[B](f: A => B): ThisC[B] + static def pure[A]: This[A] + def map[B](f: A => B): This[B] } // Generically, `pure[A]{.map(f)}^n` @@ -76,14 +75,11 @@ object blueSkyExtensions { else develop[A, F](n - 1, f).map(f) trait Monad[A] extends Functor[A] { - static type ThisC[A] <: Monad[A] - - def flatMap[B](f: A => ThisC[B]): ThisC[B] + def flatMap[B](f: A => This[B]): This[B] def map[B](f: A => B) = this.flatMap(f.andThen(pure)) } extend List[type T] : Monad[T] { - static type ThisC[A] = List[A] static def pure[A] = Nil def flatMap[B](f: A => List[B]): List[B] = this match { @@ -99,7 +95,6 @@ object blueSkyExtensions { // Iterables trait MonoIterable[A] { - static type ThisC[A] <: MonoIterable[A] static def empty: This[A] static def apply(xs: A*): This[A] @@ -107,14 +102,12 @@ object blueSkyExtensions { } trait Iterable[A] extends MonoIterable[A] { - static type ThisC[A] <: Iterable[A] - - def map[B](f: A => B): ThisC[B] - def flatMap[B](f: A => ThisC[B]): ThisC[B] + def map[B](f: A => B): This[B] + def flatMap[B](f: A => This[B]): This[B] } extend String : MonoIterable[Char] { - static type ThisC[A] = String + static type This[A] = String static def empty = "" static def apply(xs: A*) = xs.mkString @@ -123,14 +116,14 @@ object blueSkyExtensions { } extend String : Iterable[Char] { - static type ThisC[A] = IndexedSeq[A] + static type This[A] = IndexedSeq[A] def map[B](f: Char => B): IndexedSeq[B] = ... def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... } extend List[type T] : Iterable[T] { - static type ThisC[A] = List[A] + static type This[A] = List[A] static def empty = Nil static def apply(xs: A*) = (xs /: Nil)(_ :: _) diff --git a/tests/pending/pos/blueSkyExtensions1.scala b/tests/pending/pos/blueSkyExtensions1.scala new file mode 100644 index 000000000000..8ef37616e589 --- /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] = ... + } +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyExtensions2.scala b/tests/pending/pos/blueSkyExtensions2.scala index bc69a3109d38..e26a4940a1a5 100644 --- a/tests/pending/pos/blueSkyExtensions2.scala +++ b/tests/pending/pos/blueSkyExtensions2.scala @@ -112,9 +112,7 @@ object blueSkyExtensions { // Functor and Monad trait Functor[A] { - object type ThisC[A] <: Functor[A] - object def pure[A]: ThisC[A] - + static def pure[A]: This[A] def map[B](f: A => B): ThisC[B] } @@ -186,11 +184,4 @@ object blueSkyExtensions { def map[B](f: T => B): List[B] = ... def flatMap[B](f: T => List[B]): List[B] = ... } - - class Foo { - - } - object Foo { - - } } \ No newline at end of file From cfc905828de9fe0420ae4a78f6f5a63f7fe677d5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 19:03:36 +0100 Subject: [PATCH 49/50] Relax restriction about type overrides Allow several type aliases if they are the same. --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- .../src/dotty/tools/dotc/typer/RefChecks.scala | 5 ++++- tests/pos/this-types.scala | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/pos/this-types.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b8bd21b85f34..0802fb7eb898 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -991,7 +991,7 @@ object Types { case tp: TypeRef => if (tp.symbol.isClass) tp else tp.info match { - case TypeAlias(tp) => tp.dealias1(keepAnnots): @tailrec + case TypeAlias(alias) => alias.dealias1(keepAnnots): @tailrec case _ => tp } case app @ AppliedType(tycon, args) => diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2a0bafe31a2d..afb0a5642999 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -343,7 +343,10 @@ object RefChecks { if (autoOverride(member) || other.owner.is(JavaTrait) && ctx.testScala2Mode("`override' modifier required when a Java 8 default method is re-implemented", member.pos)) member.setFlag(Override) - else if (member.owner != clazz && other.owner != clazz && !(other.owner derivesFrom member.owner)) + else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other)) + () // OK, don't complain about type aliases which are equal + else if (member.owner != clazz && other.owner != clazz && + !(other.owner derivesFrom member.owner)) emitOverrideError( clazz + " inherits conflicting members:\n " + infoStringWithLocation(other) + " and\n " + infoStringWithLocation(member) diff --git a/tests/pos/this-types.scala b/tests/pos/this-types.scala new file mode 100644 index 000000000000..d75de54b3ffa --- /dev/null +++ b/tests/pos/this-types.scala @@ -0,0 +1,16 @@ +trait A { + type A_This <: A +} +trait B extends A { + type A_This = B_This + type B_This <: B +} +trait C extends A { + type A_This = C_This + type C_This <: C +} +trait D extends B with C { + type B_This = D_This + type C_This = D_This + type D_This <: D +} \ No newline at end of file From f65f8b35a18338ec58835f02b360f3a35bba1593 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Mar 2018 10:43:26 +0100 Subject: [PATCH 50/50] More escaping of "extension" --- .../dotty/tools/dotc/transform/PatmatExhaustivityTest.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 53c6e0c53238..a961860f4c08 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) @@ -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" +