From 876ba16122ee53f289996c893a97d22015e65e1c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 14:38:39 +0100 Subject: [PATCH 01/34] 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 49d59d12c1b2efeddcd502da4af5ff98f3c74468 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 15:54:27 +0100 Subject: [PATCH 02/34] 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 dc9e700cefc4..c520992d51d2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -672,6 +672,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 4946e535cc64..865c5adc2c08 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 c30fa18a807e..67ea84de9483 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -129,9 +129,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 a9ae01629f3796c6adc01b9a2eb3fe1682a7a39c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:41:22 +0100 Subject: [PATCH 03/34] 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 865c5adc2c08..a9ab0f3b8eb1 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 d8faef8cc4f74c0f23f4862d9007733f202ed1c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:43:52 +0100 Subject: [PATCH 04/34] 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 46439714d57deff2e4a00f357d6189ac2ac66f34 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:44:14 +0100 Subject: [PATCH 05/34] 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 36d5373dbb030ebbc72276368769effc24347781 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:16:17 +0100 Subject: [PATCH 06/34] 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 a9ab0f3b8eb1..85c12a58bf5d 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 08d3b3cfe3601ba179037fc39ec61a23f89c1a3d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:31:21 +0100 Subject: [PATCH 07/34] 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 989a6ae7e60c..ce63d751e43f 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 82d9261b74d302140171e190d3a4f2bd717d6c74 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 14:16:48 +0100 Subject: [PATCH 08/34] 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/FirstTransform.scala | 19 +++---- .../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 ++-- 18 files changed, 71 insertions(+), 116 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 c520992d51d2..1c1bead3b809 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -674,6 +674,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 85c12a58bf5d..d69cb1b0504f 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 719b9498671f..49e032b8b91e 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/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index c0917ac8fb2a..c65f9a5826d3 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -109,10 +109,13 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } def registerCompanion(name: TermName, forClass: Symbol): TermSymbol = { - val (modul, mcCompanion, classCompanion) = newCompanion(name, forClass) + val modul = newCompanion(name, forClass) if (ctx.owner.isClass) modul.enteredAfter(thisPhase) - mcCompanion.enteredAfter(thisPhase) - classCompanion.enteredAfter(thisPhase) + val modcls = modul.moduleClass + modcls.registerCompanion(forClass) + val cls1 = forClass.copySymDenotation() + cls1.registerCompanion(modcls) + cls1.installAfter(thisPhase) modul } @@ -133,15 +136,9 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => addMissingCompanions(reorder(stats, Nil)) } - private def newCompanion(name: TermName, forClass: Symbol)(implicit ctx: Context) = { - val modul = ctx.newCompleteModuleSymbol(forClass.owner, name, Synthetic, Synthetic, + private def newCompanion(name: TermName, forClass: Symbol)(implicit ctx: Context) = + ctx.newCompleteModuleSymbol(forClass.owner, name, Synthetic, Synthetic, defn.ObjectType :: Nil, Scopes.newScope, assocFile = forClass.asClass.assocFile) - val mc = modul.moduleClass - - val mcComp = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, forClass, mc) - val classComp = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, mc, forClass) - (modul, mcComp, classComp) - } /** elimiate self in Template */ override def transformTemplate(impl: Template)(implicit ctx: Context): Tree = { 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 67ea84de9483..934af71743b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -126,13 +126,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 9e0639a944f6..8239f84e6f9f 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 35ded6048ab83eeedcce1ff09d4c257c424daa91 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:02:04 +0100 Subject: [PATCH 09/34] 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 c65f9a5826d3..96e60bc36a14 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 1e9f8064af8529a4ecb0c4b0062a3b01b30e5c44 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:43:24 +0100 Subject: [PATCH 10/34] 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 1c1bead3b809..475d400975c1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -530,6 +530,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") @@ -672,10 +674,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") @@ -1161,6 +1159,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 d69cb1b0504f..59a42b224a2d 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 52d0a374ac113fdde66dd07d8293a2e0b5b3ed65 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:32:27 +0100 Subject: [PATCH 11/34] 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 59a42b224a2d..2e749c838b59 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 7bf765c21ae1..090f5f101d7b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -398,7 +398,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)) @@ -406,7 +406,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) @@ -444,21 +444,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 1a522f3a2bbec61f9542f6654213cdc6bccb5313 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:57:18 +0100 Subject: [PATCH 12/34] 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 933321982852f92e1f445235bfe832ee0b8dfd98 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:08:42 +0100 Subject: [PATCH 13/34] 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 929e4a36c199c0e3f60f92865351276f36dca5f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:10:15 +0100 Subject: [PATCH 14/34] 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 d191092dffd41898135f4a7a128bf00abd3e2ad0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 08:51:34 +0100 Subject: [PATCH 15/34] 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 2e749c838b59..fabeedd482f0 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 96e60bc36a14..9bd39dcdcc95 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 30c39db046ed1a1f4ef5563b721e2d5dbd751aa1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 09:29:57 +0100 Subject: [PATCH 16/34] 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 fabeedd482f0..b4ae67f46a60 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 9bd39dcdcc95..c32a77ea1f0f 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 d4e079cf30cad16d1c633ba05686d29955b61938 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 10:34:28 +0100 Subject: [PATCH 17/34] 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 475d400975c1..00063b55789f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -530,7 +530,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 @@ -1159,7 +1158,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 b4ae67f46a60..71ca3e7f3e46 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 } @@ -1853,6 +1847,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 49e032b8b91e..719b9498671f 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 827534a4bccc1e4f5eba98077bce143eddcf1841 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 17:58:01 +0100 Subject: [PATCH 18/34] 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 33cd1eff7b76d0de2f5ca757c491f5e3c0805ce3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 26 Feb 2018 14:17:40 +0100 Subject: [PATCH 19/34] 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 fb37b7f10bb499d46f7e47ab35aa286c62107732 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 12:00:16 +0100 Subject: [PATCH 20/34] 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 1c135246245487a7927b8cb7650d6337843d093e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 12:28:10 +0100 Subject: [PATCH 21/34] 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 fad0fc04700e..091a1eab354b 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 7cc1e3795804112ff1e0d6f435d565943a94a1a5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 13:22:37 +0100 Subject: [PATCH 22/34] 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 8dd9be51aa0e0211d8b1ae3f25831d810ba9cebf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 13:33:15 +0100 Subject: [PATCH 23/34] 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 f18245d682a51b97250b53d46f1ae39bd4852613 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 14:02:05 +0100 Subject: [PATCH 24/34] 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 7ace3d75564c9de6197fe6138e150e66b43f3a48 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 14:36:04 +0100 Subject: [PATCH 25/34] 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 3675d6e8033c..bf0a8eaa4e6f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2154,7 +2154,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 f338416d040655fb1df9b7770133511f6b4563a9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 17:13:36 +0100 Subject: [PATCH 26/34] 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 4dd12d3f71442e2f110283de39cb5aabb10ce4fc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 27 Feb 2018 18:08:05 +0100 Subject: [PATCH 27/34] 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 f83d3339a0c658f4a014424bc44fb1da1d6c6f48 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 27 Feb 2018 18:57:38 +0100 Subject: [PATCH 28/34] 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 1d61128df535d2d6e572774919bc411d4adace79 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Feb 2018 10:12:23 +0100 Subject: [PATCH 29/34] 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 d39ca3f6a5c6..64764bfcc3d8 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -287,6 +287,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 f37611d04ac89e86c7f939deeb55d804eec8230d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 28 Feb 2018 11:07:26 +0100 Subject: [PATCH 30/34] 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 d7570e255aedabd4c192cce59d1f2a039e2470bd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Feb 2018 12:25:38 +0100 Subject: [PATCH 31/34] 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 a0e6eb710bf23101eb0074e67bd5cc301ff85626 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Feb 2018 18:38:20 +0100 Subject: [PATCH 32/34] 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 02f9f7caabb836dc60bdbf05bef2e60ac0cc9a8f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 1 Mar 2018 18:07:27 +0100 Subject: [PATCH 33/34] 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 8f1f0f076581588cd6adc0302cb19aa324c5fb57 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Mar 2018 21:27:42 +0100 Subject: [PATCH 34/34] 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