From fb7084c4a49b54ccfe8b28c48f7f6e03928de8e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 14:38:39 +0100 Subject: [PATCH 01/33] Add opaque types: parsing & pickling Add `opaque` to syntax. Let it be parsed and stored/pickled as a flag. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 ++ compiler/src/dotty/tools/dotc/core/Flags.scala | 13 ++++++++----- .../dotty/tools/dotc/core/tasty/TastyFormat.scala | 6 +++++- .../dotty/tools/dotc/core/tasty/TreePickler.scala | 1 + .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Tokens.scala | 5 +++-- docs/docs/internals/syntax.md | 1 + 8 files changed, 22 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index f10670c42345..3f2136b8ce17 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -124,6 +124,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Sealed() extends Mod(Flags.Sealed) + case class Opaque() extends Mod(Flags.Opaque) + case class Override() extends Mod(Flags.Override) case class Abstract() extends Mod(Flags.Abstract) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 9f88fdbc0cf3..ed702f328558 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -251,9 +251,12 @@ object Flags { final val AccessorOrSealed = Accessor.toCommonFlags - /** A mutable var */ + /** A mutable var */ final val Mutable = termFlag(12, "mutable") + /** An opqaue type */ + final val Opaque = typeFlag(12, "opaque") + /** Symbol is local to current class (i.e. private[this] or protected[this] * pre: Private or Protected are also set */ @@ -264,7 +267,7 @@ object Flags { */ final val ParamAccessor = termFlag(14, "") - /** A value or class implementing a module */ + /** A value or class implementing a module */ final val Module = commonFlag(15, "module") final val ModuleVal = Module.toTermFlags final val ModuleClass = Module.toTypeFlags @@ -441,12 +444,12 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) + Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased, Opaque) /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local | - commonFlags(Mutable) + SourceModifierFlags | Module | Param | Synthetic | Package | Local + // | Mutable is subsumed by commonFlags(Opaque) from SourceModifierFlags // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index f082765d6cb7..1d45123d1ebb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -183,6 +183,7 @@ Standard-Section: "ASTs" TopLevelStat* OVERRIDE INLINE // inline method MACRO // inline method containing toplevel splices + OPAQUE // opaque type STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait @@ -302,7 +303,8 @@ object TastyFormat { final val STABLE = 31 final val MACRO = 32 final val ERASED = 33 - final val PARAMsetter = 34 + final val OPAQUE = 34 + final val PARAMsetter = 35 // Cat. 2: tag Nat @@ -450,6 +452,7 @@ object TastyFormat { | OVERRIDE | INLINE | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -506,6 +509,7 @@ object TastyFormat { case OVERRIDE => "OVERRIDE" case INLINE => "INLINE" case MACRO => "MACRO" + case OPAQUE => "OPAQUE" case STATIC => "STATIC" case OBJECT => "OBJECT" case TRAIT => "TRAIT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f7d942b4bdad..22d1a6eb3236 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -608,6 +608,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Trait) writeByte(TRAIT) if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) + if (flags is Opaque) writeByte(OPAQUE) } sym.annotations.foreach(pickleAnnotation(sym, _)) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index ddf3ef7ed765..b0df33c4fe8d 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -574,6 +574,7 @@ class TreeUnpickler(reader: TastyReader, case OVERRIDE => addFlag(Override) case INLINE => addFlag(Inline) case MACRO => addFlag(Macro) + case OPAQUE => addFlag(Opaque) case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) case TRAIT => addFlag(Trait) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9f86e7bde967..ce7e56b1cd31 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1649,6 +1649,7 @@ object Parsers { case PRIVATE => Mod.Private() case PROTECTED => Mod.Protected() case SEALED => Mod.Sealed() + case OPAQUE => Mod.Opaque() } /** Drop `private' modifier when followed by a qualifier. diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 0cc0cc16fcea..5becc9161309 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -178,6 +178,7 @@ object Tokens extends TokensCommon { final val INLINE = 62; enter(INLINE, "inline") final val ENUM = 63; enter(ENUM, "enum") final val ERASED = 64; enter(ERASED, "erased") + final val OPAQUE = 65; enter(OPAQUE, "opaque") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -198,7 +199,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, ERASED) + final val alphaKeywords = tokenRange(IF, OPAQUE) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -226,7 +227,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED) + ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED, OPAQUE) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index a377abee83f8..3c9d9ef1c6c7 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -286,6 +286,7 @@ LocalModifier ::= ‘abstract’ | ‘sealed’ | ‘implicit’ | ‘lazy’ + | ‘opaque’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ (id | ‘this’) ‘]’ From b3193a507cbd89d12c11f8d1d91100179fef366e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 15:54:27 +0100 Subject: [PATCH 02/33] Store opaque info in annotation An opaque type becomes an abstract type, with the alias stored in an OpaqueAlias annotation --- compiler/src/dotty/tools/dotc/core/Annotations.scala | 12 ++++++++++++ compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 ++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 8 ++++++++ .../dotty/tools/dotc/core/tasty/TreePickler.scala | 4 ++-- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 1 + .../src/dotty/tools/dotc/transform/SymUtils.scala | 4 +--- compiler/src/dotty/tools/dotc/typer/Checking.scala | 1 + compiler/src/dotty/tools/dotc/typer/Namer.scala | 1 + .../src/scala/annotation/internal/OpaqueAlias.scala | 11 +++++++++++ 9 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 library/src/scala/annotation/internal/OpaqueAlias.scala diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 2da8c95f0b71..0021e414a22b 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -173,6 +173,18 @@ object Annotations { else None } + /** Extractor for opaque alias annotations */ + object OpaqueAlias { + def apply(tp: Type)(implicit ctx: Context): Annotation = + Annotation(TypeTree(defn.OpaqueAliasAnnotType.appliedTo(tp))) + def unapply(ann: Annotation)(implicit ctx: Context) = + if (ann.symbol == defn.OpaqueAliasAnnot) { + val AppliedType(tycon, arg :: Nil) = ann.tree.tpe + Some(arg) + } + else None + } + def makeSourceFile(path: String)(implicit ctx: Context) = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 95c5aa94dd37..802f8c9fedcc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -683,6 +683,8 @@ class Definitions { def BodyAnnot(implicit ctx: Context) = BodyAnnotType.symbol.asClass lazy val ChildAnnotType = ctx.requiredClassRef("scala.annotation.internal.Child") def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass + lazy val OpaqueAliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.OpaqueAlias") + def OpaqueAliasAnnot(implicit ctx: Context) = OpaqueAliasAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index c190dfb7262c..a98122bb166f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -380,6 +380,14 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } + final def normalizeOpaque()(implicit ctx: Context) = { + if (is(Opaque)) { + addAnnotation(Annotation.OpaqueAlias(info)) + setFlag(Deferred) + info = TypeBounds.empty + } + } + // ------ Names ---------------------------------------------- /** The expanded name of this denotation. */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 22d1a6eb3236..88560b46b9ef 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -619,8 +619,8 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot - // inline bodies are reconstituted automatically when unpickling + case _ => ann.symbol == defn.BodyAnnot || ann.symbol == defn.OpaqueAliasAnnot + // inline bodies and opaque aliases are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b0df33c4fe8d..f4e0971576b7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -539,6 +539,7 @@ class TreeUnpickler(reader: TastyReader, // avoids space leaks by not capturing the current context forkAt(rhsStart).readTerm() }) + sym.normalizeOpaque() goto(start) sym } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index d8a98309a078..880d7f686830 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -126,9 +126,7 @@ class SymUtils(val self: Symbol) extends AnyVal { def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { if (!self.unforcedDecls.lookup(name).exists) { val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) - if (companionMethod.exists) { - companionMethod.entered - } + if (companionMethod.exists) companionMethod.entered } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 698fdaff0c15..6502d9b4bcbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -381,6 +381,7 @@ object Checking { checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`") if (sym.is(Inline)) checkApplicable(Inline, sym.isTerm && !sym.is(Mutable | Module)) if (sym.is(Lazy)) checkApplicable(Lazy, !sym.is(Method | Mutable)) + if (sym.is(Opaque)) checkApplicable(Opaque, sym.isAliasType) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 69836719c7d3..a45e6a66d26b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -842,6 +842,7 @@ class Namer { typer: Typer => addInlineInfo(denot) denot.info = typeSig(sym) Checking.checkWellFormed(sym) + denot.normalizeOpaque() denot.info = avoidPrivateLeaks(sym, sym.pos) } } diff --git a/library/src/scala/annotation/internal/OpaqueAlias.scala b/library/src/scala/annotation/internal/OpaqueAlias.scala new file mode 100644 index 000000000000..a6aaa54c6b66 --- /dev/null +++ b/library/src/scala/annotation/internal/OpaqueAlias.scala @@ -0,0 +1,11 @@ +package scala.annotation.internal + +import scala.annotation.Annotation + +/** An annotation to record the right-hand side of an opaque type. Given + * + * opaque type T = U + * + * the info of `T` is `Nothing..Any`, but `T` carries the annotation `OpaqueAlias[U]` + */ +class OpaqueAlias[T] extends Annotation From e58333892c9e5190ac7947135850f09b3e1e50c5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:41:22 +0100 Subject: [PATCH 03/33] Keep track of opaque companion links maintain the link from a module class to its opaque type companion, using the same technique as for companion classes. --- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 35 ++++++++++++------- .../tools/dotc/core/tasty/TreeUnpickler.scala | 10 ++++-- .../src/dotty/tools/dotc/typer/Namer.scala | 22 ++++++------ 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a6543572b23f..d23c4c16098f 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -145,6 +145,7 @@ object StdNames { val INITIALIZER_PREFIX: N = "initial$" val COMPANION_MODULE_METHOD: N = "companion$module" val COMPANION_CLASS_METHOD: N = "companion$class" + val COMPANION_TYPE_METHOD: N = "companion$type" val BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a98122bb166f..230cbf329f8c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -382,7 +382,7 @@ object SymDenotations { final def normalizeOpaque()(implicit ctx: Context) = { if (is(Opaque)) { - addAnnotation(Annotation.OpaqueAlias(info)) + addAnnotation(Annotation.OpaqueAlias(info.bounds.lo)) setFlag(Deferred) info = TypeBounds.empty } @@ -494,14 +494,15 @@ object SymDenotations { final def isAnonymousModuleVal(implicit ctx: Context) = this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) - /** Is this a companion class method or companion object method? + /** Is this a companion class or type method or companion object method? * These methods are generated by Symbols#synthesizeCompanionMethod * and used in SymDenotations#companionClass and * SymDenotations#companionModule . */ final def isCompanionMethod(implicit ctx: Context) = name.toTermName == nme.COMPANION_CLASS_METHOD || - name.toTermName == nme.COMPANION_MODULE_METHOD + name.toTermName == nme.COMPANION_MODULE_METHOD || + name.toTermName == nme.COMPANION_TYPE_METHOD /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods @@ -967,20 +968,28 @@ object SymDenotations { } } - /** The class with the same (type-) name as this module or module class, - * and which is also defined in the same scope and compilation unit. - * NoSymbol if this class does not exist. - */ - final def companionClass(implicit ctx: Context): Symbol = + private def companionType(name: TermName)(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol else { - val companionMethod = info.decls.denotsNamed(nme.COMPANION_CLASS_METHOD, selectPrivate).first - if (companionMethod.exists) - companionMethod.info.resultType.classSymbol - else - NoSymbol + val companionMethod = info.decls.denotsNamed(name, selectPrivate).first + if (companionMethod.exists) companionMethod.info.resultType.typeSymbol + else NoSymbol } + /** The class with the same (type-) name as this module or module class, + * and which is also defined in the same scope and compilation unit. + * NoSymbol if this class does not exist. + */ + final def companionClass(implicit ctx: Context): Symbol = + companionType(nme.COMPANION_CLASS_METHOD).suchThat(_.isClass).symbol + + /** The opaque type with the same (type-) name as this module or module class, + * and which is also defined in the same scope and compilation unit. + * NoSymbol if this type does not exist. + */ + final def companionOpaqueType(implicit ctx: Context): Symbol = + companionType(nme.COMPANION_TYPE_METHOD).suchThat(_.is(Opaque)).symbol + final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) else if (this.isClass) companionNamed(effectiveName.moduleClassName).sourceModule.moduleClass diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f4e0971576b7..5a31036991f5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -752,8 +752,14 @@ class TreeUnpickler(reader: TastyReader, def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) if (companion.exists && isCodefined) { - if (sym is Flags.ModuleClass) sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) - else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) + if (sym is Module) { + if (companion.isClass) + sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) + else if (companion.is(Opaque)) + sym.registerCompanionMethod(nme.COMPANION_TYPE_METHOD, companion) + } + else + sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) } TypeDef(readTemplate(localCtx)) } else { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a45e6a66d26b..ea0abbafe7a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -613,22 +613,24 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (claz.isClass && modl.isClass) { - ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered - ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered - } + if (modl.isClass) + if (claz.isClass) { + ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered + ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered + } + else if (claz.is(Opaque)) + ctx.synthesizeCompanionMethod(nme.COMPANION_TYPE_METHOD, claz, modl).entered } def createCompanionLinks(implicit ctx: Context): Unit = { val classDef = mutable.Map[TypeName, TypeDef]() val moduleDef = mutable.Map[TypeName, TypeDef]() - def updateCache(cdef: TypeDef): Unit = { - if (!cdef.isClassDef || cdef.mods.is(Package)) return - - if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef - else classDef(cdef.name) = cdef - } + def updateCache(cdef: TypeDef): Unit = + if (cdef.isClassDef && !cdef.mods.is(Package) || cdef.mods.is(Opaque)) { + if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef + else classDef(cdef.name) = cdef + } for (stat <- stats) expanded(stat) match { From 3e53f63cf11bfb63aeade9029242aee26e93496a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:43:52 +0100 Subject: [PATCH 04/33] Maintain companion aliases as GADT bounds --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ea0abbafe7a7..5a0ea210ba5d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -103,11 +103,20 @@ trait NamerContextOps { this: Context => /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - val localCtx: Context = ctx.fresh.setNewScope + var localCtx: FreshContext = ctx.fresh.setNewScope selfInfo match { case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => localCtx.scope.openForMutations.enter(sym) case _ => } + if (ctx.owner.is(Module)) { + val opaq = ctx.owner.companionOpaqueType + opaq.getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(Annotation.OpaqueAlias(rhs)) => + localCtx = localCtx.setFreshGADTBounds + localCtx.gadt.setBounds(opaq, TypeAlias(rhs)) + case _ => + } + } localCtx } @@ -506,7 +515,6 @@ class Namer { typer: Typer => case _ => } - def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { case t: MemberDef if t.rawComment.isDefined => ctx.docCtx.foreach(_.addDocstring(sym, t.rawComment)) From d0eec141055f584e97c4f8304138495c9c4b1a86 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 16:44:14 +0100 Subject: [PATCH 05/33] Test cases --- tests/neg/opaque.scala | 19 +++++++++++++++++++ tests/pos/opaque.scala | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/neg/opaque.scala create mode 100644 tests/pos/opaque.scala diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala new file mode 100644 index 000000000000..f1117deffef2 --- /dev/null +++ b/tests/neg/opaque.scala @@ -0,0 +1,19 @@ +object opaquetypes { + opaque val x: Int = 1 // error + + opaque class Foo // error + + opaque type T // error + + opaque type U <: String // error + + opaque type O = String + + val s: O = "" // error + + object O { + val s: O = "" // should be OK + } + +} + diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala new file mode 100644 index 000000000000..7faba38d7faa --- /dev/null +++ b/tests/pos/opaque.scala @@ -0,0 +1,23 @@ +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(`this`) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(`this` + that) + } + } +} From 8b2117b27a7723f97107bf6be4fca5aef7c8e0b6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:16:17 +0100 Subject: [PATCH 06/33] Allow for higher-kinded opaque types --- .../dotty/tools/dotc/core/SymDenotations.scala | 17 ++++++++++++++--- tests/pos/opaque.scala | 9 +++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 230cbf329f8c..f4f2b43a4ee4 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -380,11 +380,22 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } + /** If this is an opaque type alias, mark it as Deferred with empty bounds + * while storing the former right-hand side in an OpaqueAlias annotation. + */ final def normalizeOpaque()(implicit ctx: Context) = { + def abstractRHS(tp: Type): Type = tp match { + case tp: HKTypeLambda => tp.derivedLambdaType(resType = abstractRHS(tp.resType)) + case _ => defn.AnyType + } if (is(Opaque)) { - addAnnotation(Annotation.OpaqueAlias(info.bounds.lo)) - setFlag(Deferred) - info = TypeBounds.empty + info match { + case tp @ TypeAlias(alias) => + addAnnotation(Annotation.OpaqueAlias(alias)) + info = TypeBounds(defn.NothingType, abstractRHS(alias)) + setFlag(Deferred) + case _ => + } } } diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 7faba38d7faa..15d0d38d0ae4 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -21,3 +21,12 @@ object opaquetypes { } } } +object usesites { + import opaquetypes._ + import Logarithm.LogarithmOps // todo: drop + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l + l2 + val d = l3.toDouble + val l4: Logarithm = (1.0).asInstanceOf[Logarithm] +} From e0da82b2bdc09e565e938947bc86a556249a8d64 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 21 Feb 2018 18:31:21 +0100 Subject: [PATCH 07/33] Handle higher-kinded comparisons involving GADTs Higher-kinded comparisons did not account for the fact that a type constructor in a higher-kinded application could have a narrowed GADT bound. --- .../dotty/tools/dotc/core/TypeComparer.scala | 18 ++++-- tests/neg/tagging.scala | 55 +++++++++++++++++++ tests/pos/tagging.scala | 55 +++++++++++++++++++ 3 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 tests/neg/tagging.scala create mode 100644 tests/pos/tagging.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e241a32e6a50..b8edb3d5d465 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -773,9 +773,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - if (tyconIsTypeRef) recur(tp1, tp2.superType) + if (followSuperType) recur(tp1, tp2.superType) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -784,12 +784,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case param2: TypeParamRef => isMatchingApply(tp1) || canConstrain(param2) && canInstantiate(param2) || - compareLower(bounds(param2), tyconIsTypeRef = false) + compareLower(bounds(param2), followSuperType = false) case tycon2: TypeRef => isMatchingApply(tp1) || { tycon2.info match { case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) + val gbounds2 = ctx.gadt.bounds(tycon2.symbol) + if (gbounds2 == null) compareLower(info2, followSuperType = true) + else compareLower(gbounds2 & info2, followSuperType = false) case info2: ClassInfo => val base = tp1.baseType(info2.cls) if (base.exists && base.ne(tp1)) @@ -821,8 +823,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } canConstrain(param1) && canInstantiate || isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow) - case tycon1: TypeRef if tycon1.symbol.isClass => - false + case tycon1: TypeRef => + !tycon1.symbol.isClass && { + val gbounds1 = ctx.gadt.bounds(tycon1.symbol) + if (gbounds1 == null) recur(tp1.superType, tp2) + else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2) + } case tycon1: TypeProxy => recur(tp1.superType, tp2) case _ => diff --git a/tests/neg/tagging.scala b/tests/neg/tagging.scala new file mode 100644 index 000000000000..72913872c36f --- /dev/null +++ b/tests/neg/tagging.scala @@ -0,0 +1,55 @@ +import scala.reflect.ClassTag +object tagging { + + // Tagged[S, T] means that S is tagged with T + opaque type Tagged[X, Y] = X + + object Tagged { + def tag[S, T](s: S): Tagged[S, T] = (s: S) + def untag[S, T](st: Tagged[S, T]): S = st + + def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs + def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst + + implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] = + ct + } + + import Tagged._ + + type @@[S, T] = Tagged[S, T] + + implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { + def untag: S = Tagged.untag(st) + } + + implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal { + def untags: F[S] = Tagged.untags(fs) + } + + implicit class TagOps[S](s: S) extends AnyVal { + def tag[T]: S @@ T = Tagged.tag(s) + } + + implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal { + def tags[T]: F[S @@ T] = Tagged.tags(fs) + } + + trait Meter + trait Foot + trait Fathom + + val x: Double @@ Meter = (1e7).tag[Meter] + val y: Double @@ Foot = (123.0).tag[Foot] + val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter] + + val o: Ordering[Double] = implicitly + val om: Ordering[Double @@ Meter] = o.tags[Meter] + om.compare(x, x) // 0 + om.compare(x, y) // error + xs.min(om) // 1.0 + xs.min(o) // error + + // uses ClassTag[Double] via 'Tagged.taggedClassTag'. + val ys = new Array[Double @@ Foot](20) +} diff --git a/tests/pos/tagging.scala b/tests/pos/tagging.scala new file mode 100644 index 000000000000..da2ba65f3ede --- /dev/null +++ b/tests/pos/tagging.scala @@ -0,0 +1,55 @@ +import scala.reflect.ClassTag +object tagging { + + // Tagged[S, T] means that S is tagged with T + opaque type Tagged[X, Y] = X + + object Tagged { + def tag[S, T](s: S): Tagged[S, T] = (s: S) + def untag[S, T](st: Tagged[S, T]): S = st + + def tags[F[_], S, T](fs: F[S]): F[Tagged[S, T]] = fs + def untags[F[_], S, T](fst: F[Tagged[S, T]]): F[S] = fst + + implicit def taggedClassTag[S, T](implicit ct: ClassTag[S]): ClassTag[Tagged[S, T]] = + ct + } + + import Tagged._ + + type @@[S, T] = Tagged[S, T] + + implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { + def untag: S = Tagged.untag(st) + } + + implicit class UntagsOps[F[_], S, T](fs: F[S @@ T]) extends AnyVal { + def untags: F[S] = Tagged.untags(fs) + } + + implicit class TagOps[S](s: S) extends AnyVal { + def tag[T]: S @@ T = Tagged.tag(s) + } + + implicit class TagsOps[F[_], S](fs: F[S]) extends AnyVal { + def tags[T]: F[S @@ T] = Tagged.tags(fs) + } + + trait Meter + trait Foot + trait Fathom + + val x: Double @@ Meter = (1e7).tag[Meter] + val y: Double @@ Foot = (123.0).tag[Foot] + val xs: Array[Double @@ Meter] = Array(1.0, 2.0, 3.0).tags[Meter] + + val o: Ordering[Double] = implicitly + val om: Ordering[Double @@ Meter] = o.tags[Meter] + om.compare(x, x) // 0 + // om.compare(x, y) // does not compile + xs.min(om) // 1.0 + // xs.min(o) // does not compile + + // uses ClassTag[Double] via 'Tagged.taggedClassTag'. + val ys = new Array[Double @@ Foot](20) +} From 224752917fbc27f83d15cb187fe86844417fd078 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 14:16:48 +0100 Subject: [PATCH 08/33] Change companion detection scheme The previous scheme, based on "magic" methods could not accommodate links from opaque types to their companion objects because there was no way to put a magic method on the opaque type. So we now use Annotations instead, which leads to some simplifications. Note: when comparing the old and new scheme I noted that the links from companion object of a value class to the value class itself broke after erasure, until they were restored in RestoreScopes. This looked unintended to me. The new scheme keeps the links unbroken throughout. --- .../dotty/tools/dotc/core/Annotations.scala | 16 ++++-- .../dotty/tools/dotc/core/Definitions.scala | 2 + .../src/dotty/tools/dotc/core/StdNames.scala | 3 - .../tools/dotc/core/SymDenotations.scala | 56 ++++++++----------- .../src/dotty/tools/dotc/core/Symbols.scala | 9 --- .../dotty/tools/dotc/core/TypeErasure.scala | 5 +- .../dotc/core/classfile/ClassfileParser.scala | 6 +- .../tools/dotc/core/tasty/TreePickler.scala | 6 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 12 ++-- .../core/unpickleScala2/Scala2Unpickler.scala | 7 +-- .../dotc/printing/DecompilerPrinter.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 6 +- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 3 +- .../tools/dotc/transform/RestoreScopes.scala | 12 ---- .../dotty/tools/dotc/transform/SymUtils.scala | 7 --- .../tools/dotc/transform/TreeChecker.scala | 5 +- .../src/dotty/tools/dotc/typer/Namer.scala | 11 ++-- 17 files changed, 63 insertions(+), 105 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 0021e414a22b..a8f46a3ba52f 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -173,18 +173,24 @@ object Annotations { else None } - /** Extractor for opaque alias annotations */ - object OpaqueAlias { + /** A generic extractor for annotations carrying types */ + class TypeHintExtractor(cls: Context => ClassSymbol) { def apply(tp: Type)(implicit ctx: Context): Annotation = - Annotation(TypeTree(defn.OpaqueAliasAnnotType.appliedTo(tp))) - def unapply(ann: Annotation)(implicit ctx: Context) = - if (ann.symbol == defn.OpaqueAliasAnnot) { + Annotation(TypeTree(cls(ctx).typeRef.appliedTo(tp))) + def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] = + if (ann.symbol == cls(ctx)) { val AppliedType(tycon, arg :: Nil) = ann.tree.tpe Some(arg) } else None } + /** Extractor for opaque alias annotations */ + object OpaqueAlias extends TypeHintExtractor(implicit ctx => defn.OpaqueAliasAnnot) + + /** Extractpr for linked type annotations */ + object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) + def makeSourceFile(path: String)(implicit ctx: Context) = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 802f8c9fedcc..9a582456f1c5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -685,6 +685,8 @@ class Definitions { def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass lazy val OpaqueAliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.OpaqueAlias") def OpaqueAliasAnnot(implicit ctx: Context) = OpaqueAliasAnnotType.symbol.asClass + lazy val LinkedTypeAnnotType = ctx.requiredClassRef("scala.annotation.internal.LinkedType") + def LinkedTypeAnnot(implicit ctx: Context) = LinkedTypeAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index d23c4c16098f..5abefbf00e95 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -143,9 +143,6 @@ object StdNames { val WHILE_PREFIX: N = "while$" val DEFAULT_EXCEPTION_NAME: N = "ex$" val INITIALIZER_PREFIX: N = "initial$" - val COMPANION_MODULE_METHOD: N = "companion$module" - val COMPANION_CLASS_METHOD: N = "companion$class" - val COMPANION_TYPE_METHOD: N = "companion$type" val BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f4f2b43a4ee4..6fdda2e07e6b 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -105,11 +105,14 @@ trait SymDenotations { this: Context => } /** Configurable: Accept stale symbol with warning if in IDE */ - def staleOK = Config.ignoreStaleInIDE && mode.is(Mode.Interactive) + def staleOK(denot: SingleDenotation) = + Config.ignoreStaleInIDE && mode.is(Mode.Interactive) || + denot.symbol.owner == defn.LinkedTypeAnnot + // LinkedType's type parameter might be stale if LinkedType itself is compiled since it is loaded early /** Possibly accept stale symbol with warning if in IDE */ def acceptStale(denot: SingleDenotation): Boolean = - staleOK && { + staleOK(denot) && { ctx.echo(denot.staleSymbolMsg) true } @@ -505,16 +508,6 @@ object SymDenotations { final def isAnonymousModuleVal(implicit ctx: Context) = this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) - /** Is this a companion class or type method or companion object method? - * These methods are generated by Symbols#synthesizeCompanionMethod - * and used in SymDenotations#companionClass and - * SymDenotations#companionModule . - */ - final def isCompanionMethod(implicit ctx: Context) = - name.toTermName == nme.COMPANION_CLASS_METHOD || - name.toTermName == nme.COMPANION_MODULE_METHOD || - name.toTermName == nme.COMPANION_TYPE_METHOD - /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods * and used in ElimErasedValueType. @@ -626,12 +619,9 @@ object SymDenotations { * - not an accessor * - not a label * - not an anonymous function - * - not a companion method */ final def isRealMethod(implicit ctx: Context) = - this.is(Method, butNot = AccessorOrLabel) && - !isAnonymousFunction && - !isCompanionMethod + this.is(Method, butNot = AccessorOrLabel) && !isAnonymousFunction /** Is this a getter? */ final def isGetter(implicit ctx: Context) = @@ -964,27 +954,29 @@ object SymDenotations { final def enclosingPackageClass(implicit ctx: Context): Symbol = if (this is PackageClass) symbol else owner.enclosingPackageClass + /** Register target as a companion */ + def registerCompanion(target: Symbol)(implicit ctx: Context) = + if (exists && target.exists && !unforcedIsAbsent && !target.unforcedIsAbsent && + !myAnnotations.exists(_.symbol == defn.LinkedTypeAnnot)) + addAnnotation(Annotation.LinkedType(target.typeRef)) + /** The module object with the same (term-) name as this class or module class, * and which is also defined in the same scope and compilation unit. * NoSymbol if this module does not exist. */ - final def companionModule(implicit ctx: Context): Symbol = { - if (this.flagsUNSAFE is Flags.Module) this.sourceModule - else { - val companionMethod = info.decls.denotsNamed(nme.COMPANION_MODULE_METHOD, selectPrivate).first - if (companionMethod.exists) - companionMethod.info.resultType.classSymbol.sourceModule - else - NoSymbol + final def companionModule(implicit ctx: Context): Symbol = + if (is(Module)) sourceModule + else getAnnotation(defn.LinkedTypeAnnot) match { + case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol.sourceModule + case _ => NoSymbol } - } - private def companionType(name: TermName)(implicit ctx: Context): Symbol = + private def companionType(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol - else { - val companionMethod = info.decls.denotsNamed(name, selectPrivate).first - if (companionMethod.exists) companionMethod.info.resultType.typeSymbol - else NoSymbol + else if (is(ModuleVal)) moduleClass.denot.companionType + else getAnnotation(defn.LinkedTypeAnnot) match { + case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol + case _ => NoSymbol } /** The class with the same (type-) name as this module or module class, @@ -992,14 +984,14 @@ object SymDenotations { * NoSymbol if this class does not exist. */ final def companionClass(implicit ctx: Context): Symbol = - companionType(nme.COMPANION_CLASS_METHOD).suchThat(_.isClass).symbol + companionType.suchThat(_.isClass).symbol /** The opaque type with the same (type-) name as this module or module class, * and which is also defined in the same scope and compilation unit. * NoSymbol if this type does not exist. */ final def companionOpaqueType(implicit ctx: Context): Symbol = - companionType(nme.COMPANION_TYPE_METHOD).suchThat(_.is(Opaque)).symbol + companionType.suchThat(_.is(Opaque)).symbol final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 5b7a6b311ab2..2bae0b511a72 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -187,15 +187,6 @@ trait Symbols { this: Context => val companionMethodFlags = Flags.Synthetic | Flags.Private | Flags.Method - def synthesizeCompanionMethod(name: Name, target: SymDenotation, owner: SymDenotation)(implicit ctx: Context) = - if (owner.exists && target.exists && !owner.unforcedIsAbsent && !target.unforcedIsAbsent) { - val existing = owner.unforcedDecls.lookup(name) - - existing.orElse{ - ctx.newSymbol(owner.symbol, name, companionMethodFlags , ExprType(target.typeRef)) - } - } else NoSymbol - /** Create a package symbol with associated package class * from its non-info fields and a lazy type for loading the package's members. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index a4566e729b72..2a668520f152 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -158,9 +158,6 @@ object TypeErasure { * - For $asInstanceOf : [T]T * - For $isInstanceOf : [T]Boolean * - For all abstract types : = ? - * - For companion methods : the erasure of their type with semiEraseVCs = false. - * The signature of these methods are used to keep a - * link between companions and should not be semi-erased. * - For Java-defined symbols: : the erasure of their type with isJava = true, * semiEraseVCs = false. Semi-erasure never happens in Java. * - For all other symbols : the semi-erasure of their types, with @@ -168,7 +165,7 @@ object TypeErasure { */ def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { val isJava = sym is JavaDefined - val semiEraseVCs = !isJava && !sym.isCompanionMethod + val semiEraseVCs = !isJava val erase = erasureFn(isJava, semiEraseVCs, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 2898db135a3b..efd48d0fa71c 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -164,10 +164,8 @@ class ClassfileParser( classInfo = parseAttributes(classRoot.symbol, classInfo) if (isAnnotation) addAnnotationConstructor(classInfo) - val companionClassMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, classRoot, moduleRoot) - if (companionClassMethod.exists) companionClassMethod.entered - val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, moduleRoot, classRoot) - if (companionModuleMethod.exists) companionModuleMethod.entered + classRoot.registerCompanion(moduleRoot.symbol) + moduleRoot.registerCompanion(classRoot.symbol) setClassInfo(classRoot, classInfo) setClassInfo(moduleRoot, staticInfo) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 88560b46b9ef..0feddd48eeef 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -619,8 +619,10 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot || ann.symbol == defn.OpaqueAliasAnnot - // inline bodies and opaque aliases are reconstituted automatically when unpickling + case _ => + val sym = ann.symbol + sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot || sym == defn.LinkedTypeAnnot + // these are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 5a31036991f5..7e6e7c8763e8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -752,14 +752,12 @@ class TreeUnpickler(reader: TastyReader, def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) if (companion.exists && isCodefined) { - if (sym is Module) { - if (companion.isClass) - sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) - else if (companion.is(Opaque)) - sym.registerCompanionMethod(nme.COMPANION_TYPE_METHOD, companion) + if (companion.isClass) + sym.registerCompanion(companion) + else if (sym.is(Module) && companion.is(Opaque)) { + sym.registerCompanion(companion) + companion.registerCompanion(sym) } - else - sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) } TypeDef(readTemplate(localCtx)) } else { diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index f7dbda218e0e..83c0baf4acec 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -120,11 +120,8 @@ object Scala2Unpickler { val scalacCompanion = denot.classSymbol.scalacLinkedClass def registerCompanionPair(module: Symbol, claz: Symbol) = { - import transform.SymUtils._ - module.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, claz) - if (claz.isClass) { - claz.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, module) - } + module.registerCompanion(claz) + if (claz.isClass) claz.registerCompanion(module) } if (denot.flagsUNSAFE is Module) { diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index 392d719cfefd..c590adf2206e 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -13,7 +13,7 @@ import scala.language.implicitConversions class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = - annots.filter(_.tpe != defn.SourceFileAnnotType) + super.filterModTextAnnots(annots).filter(_.tpe != defn.SourceFileAnnotType) override protected def blockText[T >: Untyped](trees: List[Trees.Tree[T]]): Text = { super.blockText(trees.filterNot(_.isInstanceOf[Closure[_]])) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48bc992f8a47..ce00faee2d16 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -694,7 +694,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } - protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = annots + protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = + annots filter { + case tpt: TypeTree[_] => tpt.typeOpt.typeSymbol != defn.LinkedTypeAnnot + case _ => true + } def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name)) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4be68b0879d4..cea2975da291 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -212,8 +212,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // Synthetic methods that are always present do not affect the API // and can therefore be ignored. - def alwaysPresent(s: Symbol) = - s.isCompanionMethod || (csym.is(ModuleClass) && s.isConstructor) + def alwaysPresent(s: Symbol) = csym.is(ModuleClass) && s.isConstructor val decls = cinfo.decls.filter(!alwaysPresent(_)) val apiDecls = apiDefinitions(decls) diff --git a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala index b88d46fdda6a..e21dbc783b0f 100644 --- a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala +++ b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala @@ -44,18 +44,6 @@ class RestoreScopes extends MiniPhase with IdentityDenotTransformer { thisPhase val cls = tree.symbol.asClass val pkg = cls.owner.asClass - // Bring back companion links - val companionClass = cls.info.decls.lookup(nme.COMPANION_CLASS_METHOD) - val companionModule = cls.info.decls.lookup(nme.COMPANION_MODULE_METHOD) - - if (companionClass.exists) { - restoredDecls.enter(companionClass) - } - - if (companionModule.exists) { - restoredDecls.enter(companionModule) - } - pkg.enter(cls) val cinfo = cls.classInfo tree.symbol.copySymDenotation( diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 880d7f686830..3873fbfc6fb4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -123,13 +123,6 @@ class SymUtils(val self: Symbol) extends AnyVal { self } - def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { - if (!self.unforcedDecls.lookup(name).exists) { - val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) - if (companionMethod.exists) companionMethod.entered - } - } - /** If this symbol is an enum value or a named class, register it as a child * in all direct parent classes which are sealed. * @param @late If true, register only inaccessible children (all others are already diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2fc388373f12..e8274d2861a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -370,10 +370,7 @@ class TreeChecker extends Phase with SymTransformer { checkOwner(impl.constr) def isNonMagicalMethod(x: Symbol) = - x.is(Method) && - !x.isCompanionMethod && - !x.isValueClassConvertMethod && - !(x.is(Macro) && ctx.phase.refChecked) + x.is(Method) && !x.isValueClassConvertMethod && !(x.is(Macro) && ctx.phase.refChecked) val symbolsNotDefined = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5a0ea210ba5d..2d7ba3347c4c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -621,13 +621,10 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (modl.isClass) - if (claz.isClass) { - ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, claz, modl).entered - ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, modl, claz).entered - } - else if (claz.is(Opaque)) - ctx.synthesizeCompanionMethod(nme.COMPANION_TYPE_METHOD, claz, modl).entered + if (modl.isClass && (claz.isClass || claz.is(Opaque))) { + modl.registerCompanion(claz) + claz.registerCompanion(modl) + } } def createCompanionLinks(implicit ctx: Context): Unit = { From c70bcb0b78f1184b33bc967c8ed06883cdce5084 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:02:04 +0100 Subject: [PATCH 09/33] Eliminate boxing for opaque types FirstTransform rewrites opaque type aliases to normal aliases, so no boxing is introduced in erasure. --- .../tools/dotc/transform/FirstTransform.scala | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 14bb0c00ceba..aaf3616cb596 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -15,6 +15,7 @@ import Contexts.Context import Symbols._ import SymDenotations._ import Decorators._ +import Annotations._ import dotty.tools.dotc.core.Annotations.ConcreteAnnotation import dotty.tools.dotc.core.Denotations.SingleDenotation import scala.collection.mutable @@ -34,13 +35,14 @@ object FirstTransform { * - eliminates some kinds of trees: Imports, NamedArgs * - stubs out native methods * - eliminates self tree in Template and self symbol in ClassInfo + * - rewrites opaque type aliases to normal alias types * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types * - drops branches of ifs using the rules * if (true) A else B ==> A * if (false) A else B ==> B */ -class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => +class FirstTransform extends MiniPhase with SymTransformer { thisPhase => import ast.tpd._ override def phaseName = FirstTransform.name @@ -57,12 +59,27 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => ctx } - /** eliminate self symbol in ClassInfo */ - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + /** Two transforms: + * 1. eliminate self symbol in ClassInfo + * 2. Rewrite opaque type aliases to normal alias types + */ + def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = sym.info match { case tp @ ClassInfo(_, _, _, _, self: Symbol) => - tp.derivedClassInfo(selfInfo = self.info) + sym.copySymDenotation(info = tp.derivedClassInfo(selfInfo = self.info)) + .copyCaches(sym, ctx.phase.next) case _ => - tp + if (sym.is(Opaque)) + sym.getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(Annotation.OpaqueAlias(rhs)) => + val result = sym.copySymDenotation(info = TypeAlias(rhs)) + result.removeAnnotation(defn.OpaqueAliasAnnot) + result.resetFlag(Opaque) + result.resetFlag(Deferred) + result + case _ => + sym + } + else sym } override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass From af5c199d574b68aa03e9a1fe05e499daca02444d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 16:43:24 +0100 Subject: [PATCH 10/33] Make OpaqueAlias and LinkedType special classes It's overall simpler to just define them internally in Definitions --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 8 ++++---- compiler/src/dotty/tools/dotc/core/StdNames.scala | 2 ++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 7 ++----- .../src/scala/annotation/internal/OpaqueAlias.scala | 11 ----------- 4 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 library/src/scala/annotation/internal/OpaqueAlias.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9a582456f1c5..22eebd20684a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -537,6 +537,8 @@ class Definitions { lazy val EqualsPatternClass = enterSpecialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType)) lazy val RepeatedParamClass = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) + lazy val LinkedTypeAnnot = enterSpecialPolyClass(tpnme.LINKED_TYPE, EmptyFlags, Seq(AnyType)) + lazy val OpaqueAliasAnnot = enterSpecialPolyClass(tpnme.OPAQUE_ALIAS, EmptyFlags, Seq(AnyType)) // fundamental classes lazy val StringClass = ctx.requiredClass("java.lang.String") @@ -683,10 +685,6 @@ class Definitions { def BodyAnnot(implicit ctx: Context) = BodyAnnotType.symbol.asClass lazy val ChildAnnotType = ctx.requiredClassRef("scala.annotation.internal.Child") def ChildAnnot(implicit ctx: Context) = ChildAnnotType.symbol.asClass - lazy val OpaqueAliasAnnotType = ctx.requiredClassRef("scala.annotation.internal.OpaqueAlias") - def OpaqueAliasAnnot(implicit ctx: Context) = OpaqueAliasAnnotType.symbol.asClass - lazy val LinkedTypeAnnotType = ctx.requiredClassRef("scala.annotation.internal.LinkedType") - def LinkedTypeAnnot(implicit ctx: Context) = LinkedTypeAnnotType.symbol.asClass lazy val CovariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween") def CovariantBetweenAnnot(implicit ctx: Context) = CovariantBetweenAnnotType.symbol.asClass lazy val ContravariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween") @@ -1175,6 +1173,8 @@ class Definitions { AnyKindClass, RepeatedParamClass, ByNameParamClass2x, + LinkedTypeAnnot, + OpaqueAliasAnnot, AnyValClass, NullClass, NothingClass, diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 5abefbf00e95..f69c83c62235 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -189,6 +189,8 @@ object StdNames { final val EQUALS_PATTERN: N = "" final val LOCAL_CHILD: N = "" final val REPEATED_PARAM_CLASS: N = "" + final val OPAQUE_ALIAS: N = "" + final val LINKED_TYPE: N = "" final val WILDCARD_STAR: N = "_*" final val REIFY_TREECREATOR_PREFIX: N = "$treecreator" final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 6fdda2e07e6b..da6eba795639 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -105,14 +105,11 @@ trait SymDenotations { this: Context => } /** Configurable: Accept stale symbol with warning if in IDE */ - def staleOK(denot: SingleDenotation) = - Config.ignoreStaleInIDE && mode.is(Mode.Interactive) || - denot.symbol.owner == defn.LinkedTypeAnnot - // LinkedType's type parameter might be stale if LinkedType itself is compiled since it is loaded early + def staleOK = Config.ignoreStaleInIDE && mode.is(Mode.Interactive) /** Possibly accept stale symbol with warning if in IDE */ def acceptStale(denot: SingleDenotation): Boolean = - staleOK(denot) && { + staleOK && { ctx.echo(denot.staleSymbolMsg) true } diff --git a/library/src/scala/annotation/internal/OpaqueAlias.scala b/library/src/scala/annotation/internal/OpaqueAlias.scala deleted file mode 100644 index a6aaa54c6b66..000000000000 --- a/library/src/scala/annotation/internal/OpaqueAlias.scala +++ /dev/null @@ -1,11 +0,0 @@ -package scala.annotation.internal - -import scala.annotation.Annotation - -/** An annotation to record the right-hand side of an opaque type. Given - * - * opaque type T = U - * - * the info of `T` is `Nothing..Any`, but `T` carries the annotation `OpaqueAlias[U]` - */ -class OpaqueAlias[T] extends Annotation From f2cf353e91d2ee83beaa1af827fee12895838908 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:32:27 +0100 Subject: [PATCH 11/33] Make implicit scope include companion objects of opaque types --- .../tools/dotc/core/SymDenotations.scala | 5 +++ .../dotty/tools/dotc/typer/Implicits.scala | 33 ++++++++++--------- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- tests/neg/tagging.scala | 2 -- tests/pos/opaque.scala | 4 +-- tests/pos/tagging.scala | 2 -- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index da6eba795639..2082c5ad600f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -551,6 +551,11 @@ object SymDenotations { /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(implicit ctx: Context) = this is DeferredOrTypeParam + /** Can this symbol have a companion module? + * This is the case if it is a class or an opaque type alias. + */ + final def canHaveCompanion(implicit ctx: Context) = isClass || is(Opaque) + /** Is this the denotation of a self symbol of some class? * This is the case if one of two conditions holds: * 1. It is the symbol referred to in the selfInfo part of the ClassInfo diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f11f75b704c4..f50360bdd212 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -394,7 +394,7 @@ trait ImplicitRunInfo { self: Run => override implicit protected val ctx: Context = liftingCtx override def stopAtStatic = true def apply(tp: Type) = tp match { - case tp: TypeRef if tp.symbol.isAbstractOrAliasType => + case tp: TypeRef if !tp.symbol.canHaveCompanion => val pre = tp.prefix def joinClass(tp: Type, cls: ClassSymbol) = AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner)) @@ -402,7 +402,7 @@ trait ImplicitRunInfo { self: Run => (lead /: tp.classSymbols)(joinClass) case tp: TypeVar => apply(tp.underlying) - case tp: AppliedType if !tp.tycon.typeSymbol.isClass => + case tp: AppliedType if !tp.tycon.typeSymbol.canHaveCompanion => def applyArg(arg: Type) = arg match { case TypeBounds(lo, hi) => AndType.make(lo, hi) case WildcardType(TypeBounds(lo, hi)) => AndType.make(lo, hi) @@ -440,21 +440,24 @@ trait ImplicitRunInfo { self: Run => case tp: NamedType => val pre = tp.prefix comps ++= iscopeRefs(pre) - def addClassScope(cls: ClassSymbol): Unit = { - def addRef(companion: TermRef): Unit = { - val compSym = companion.symbol - if (compSym is Package) - addRef(companion.select(nme.PACKAGE)) - else if (compSym.exists) - comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] - } - def addParentScope(parent: Type): Unit = - iscopeRefs(tp.baseType(parent.typeSymbol)) foreach addRef - val companion = cls.companionModule + def addRef(companion: TermRef): Unit = { + val compSym = companion.symbol + if (compSym is Package) + addRef(companion.select(nme.PACKAGE)) + else if (compSym.exists) + comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] + } + def addCompanionOf(sym: Symbol) = { + val companion = sym.companionModule if (companion.exists) addRef(companion.termRef) - cls.classParents foreach addParentScope } - tp.classSymbols(liftingCtx) foreach addClassScope + def addClassScope(cls: ClassSymbol): Unit = { + addCompanionOf(cls) + for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.typeSymbol))) + addRef(ref) + } + if (tp.widen.typeSymbol.is(Opaque)) addCompanionOf(tp.widen.typeSymbol) + else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 2d7ba3347c4c..5cd627e972ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -621,7 +621,7 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (modl.isClass && (claz.isClass || claz.is(Opaque))) { + if (modl.isClass && claz.canHaveCompanion) { modl.registerCompanion(claz) claz.registerCompanion(modl) } diff --git a/tests/neg/tagging.scala b/tests/neg/tagging.scala index 72913872c36f..6ad97fde3632 100644 --- a/tests/neg/tagging.scala +++ b/tests/neg/tagging.scala @@ -15,8 +15,6 @@ object tagging { ct } - import Tagged._ - type @@[S, T] = Tagged[S, T] implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 15d0d38d0ae4..36c7ba6d8746 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -23,10 +23,10 @@ object opaquetypes { } object usesites { import opaquetypes._ - import Logarithm.LogarithmOps // todo: drop val l = Logarithm(1.0) val l2 = Logarithm(2.0) - val l3 = l + l2 + val l3 = l * l2 + val l4 = l * l2 val d = l3.toDouble val l4: Logarithm = (1.0).asInstanceOf[Logarithm] } diff --git a/tests/pos/tagging.scala b/tests/pos/tagging.scala index da2ba65f3ede..3a9caa2129f9 100644 --- a/tests/pos/tagging.scala +++ b/tests/pos/tagging.scala @@ -15,8 +15,6 @@ object tagging { ct } - import Tagged._ - type @@[S, T] = Tagged[S, T] implicit class UntagOps[S, T](st: S @@ T) extends AnyVal { From 71ae9433a652fb568fae8b70f69c7fd80397481f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 17:57:18 +0100 Subject: [PATCH 12/33] Add reference documentation --- docs/docs/reference/opaques.md | 56 ++++++++++++++++++++++++++++++++++ docs/sidebar.yml | 2 ++ tests/neg/opaque.scala | 29 ++++++++++++++++++ tests/pos/opaque.scala | 2 +- 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 docs/docs/reference/opaques.md diff --git a/docs/docs/reference/opaques.md b/docs/docs/reference/opaques.md new file mode 100644 index 000000000000..0acaca050f3d --- /dev/null +++ b/docs/docs/reference/opaques.md @@ -0,0 +1,56 @@ +--- +layout: doc-page +title: "Opaque Type Aliases" +--- + +Opaque types aliases provide type abstraction without any overhead. Example: + +```scala +opaque type Logarithm = Double +``` + +This introduces `Logarithm` as a new type, which is implemented as `Double` but is different from it. The fact that `Logarithm` is the same as `Double` is only known in the companion object of `Logarithm`. Here is a possible companion object: + +```scala +object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(`this`) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(`this` + that) + } +} +``` + +The companion object contains with the `apply` and `safe` methods ways to convert from doubles to `Logarithm` values. It also adds an `exponent` function and a decorator that implements `+` and `*` on logarithm values, as well as a conversion `toDouble`. All this is possible because within object `Logarithm`, the type `Logarithm` is just an alias of `Double`. + +Outside the companion object, `Logarithm` is treated as a new abstract type. So the +following operations would be valid because they use functionality implemented in the `Logarithm` object. + +```scala + val l = Logarithm(1.0) + val l3 = l * l2 + val l4 = l + l2 +``` + +But the following operations would lead to type errors: + +```scala +val d: Double = l // error: found: Logarithm, required: Double +val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm +l * 2 // error: found: Int(2), required: Logarithm +l / l2 // error: `/` is not a member fo Logarithm +``` + +For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html). \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index ca456b11af65..e247849662e0 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -47,6 +47,8 @@ sidebar: url: docs/reference/inline.html - title: Meta Programming url: docs/reference/principled-meta-programming.html + - title: Opaque Type Aliases + url: docs/reference/opaques.html - title: By-Name Implicits url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala index f1117deffef2..efb06e8582c9 100644 --- a/tests/neg/opaque.scala +++ b/tests/neg/opaque.scala @@ -17,3 +17,32 @@ object opaquetypes { } +object logs { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + // Extension methods define opaque types' public APIs + implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(`this`) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(`this` + that) + } + } + + val l = Logarithm(2.0) + val d: Double = l // error: found: Logarithm, required: Double + val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm + l * 2 // error: found: Int(2), required: Logarithm + l / l2 // error: `/` is not a member fo Logarithm +} diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 36c7ba6d8746..403053bff643 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -26,7 +26,7 @@ object usesites { val l = Logarithm(1.0) val l2 = Logarithm(2.0) val l3 = l * l2 - val l4 = l * l2 + val l4 = l + l2 val d = l3.toDouble val l4: Logarithm = (1.0).asInstanceOf[Logarithm] } From 89dd5375878f5650db34dff18da65d5fb6fd01d7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:08:42 +0100 Subject: [PATCH 13/33] Fix test --- tests/pos/opaque.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala index 403053bff643..036f75b1cc05 100644 --- a/tests/pos/opaque.scala +++ b/tests/pos/opaque.scala @@ -1,3 +1,4 @@ +import Predef.{any2stringadd => _, _} object opaquetypes { opaque type Logarithm = Double @@ -26,7 +27,10 @@ object usesites { val l = Logarithm(1.0) val l2 = Logarithm(2.0) val l3 = l * l2 - val l4 = l + l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd val d = l3.toDouble - val l4: Logarithm = (1.0).asInstanceOf[Logarithm] + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] } From 893eebb07eb49584301d6defdcc028054e3e4768 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 22 Feb 2018 18:10:15 +0100 Subject: [PATCH 14/33] Fix indentation --- docs/docs/reference/opaques.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/reference/opaques.md b/docs/docs/reference/opaques.md index 0acaca050f3d..e4a2867049ef 100644 --- a/docs/docs/reference/opaques.md +++ b/docs/docs/reference/opaques.md @@ -47,10 +47,10 @@ following operations would be valid because they use functionality implemented i But the following operations would lead to type errors: ```scala -val d: Double = l // error: found: Logarithm, required: Double -val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm -l * 2 // error: found: Int(2), required: Logarithm -l / l2 // error: `/` is not a member fo Logarithm + val d: Double = l // error: found: Logarithm, required: Double + val l2: Logarithm = 1.0 // error: found: Double, required: Logarithm + l * 2 // error: found: Int(2), required: Logarithm + l / l2 // error: `/` is not a member fo Logarithm ``` For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html). \ No newline at end of file From 77317b068b96b1f1067c6009b6e53906073325b8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 08:51:34 +0100 Subject: [PATCH 15/33] Treat companions of opaque types specially Make them part of the OpaqueAlias annotation. This is has two benefits - opaque types stop being companions after FirstTransform. Previously, the LinkedType annotations would persist even though the Opaque flag was reset. - we gain the freedom to implement companions between classes differently. --- .../dotty/tools/dotc/core/Annotations.scala | 18 ++++++++++++++++-- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotty/tools/dotc/core/SymDenotations.scala | 18 +++++++++++++----- .../tools/dotc/core/tasty/TreeUnpickler.scala | 13 +++---------- .../tools/dotc/transform/FirstTransform.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index a8f46a3ba52f..fec280eef80b 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -186,9 +186,23 @@ object Annotations { } /** Extractor for opaque alias annotations */ - object OpaqueAlias extends TypeHintExtractor(implicit ctx => defn.OpaqueAliasAnnot) + object OpaqueAlias { + def apply(tp: Type, companion: Symbol)(implicit ctx: Context): Annotation = { + val arg = if (companion.exists) RefinedType(tp, nme.companion, companion.termRef) else tp + Annotation(TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg))) + } + def unapply(ann: Annotation)(implicit ctx: Context): Option[(Type, Symbol)] = + if (ann.symbol == defn.OpaqueAliasAnnot) { + ann.tree.tpe match { + case AppliedType(_, RefinedType(tp, nme.companion, ref) :: Nil) => Some((tp, ref.termSymbol)) + case AppliedType(_, tp :: Nil) => Some((tp, NoSymbol)) + case _ => None + } + } + else None + } - /** Extractpr for linked type annotations */ + /** Extractor for linked type annotations */ object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) def makeSourceFile(path: String)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index f69c83c62235..54bbc139701a 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -395,6 +395,7 @@ object StdNames { val classOf: N = "classOf" val clone_ : N = "clone" // val conforms : N = "conforms" // Dotty deviation: no special treatment of conforms, so the occurrence of the name here would cause to unintended implicit shadowing. Should find a less common name for it in Predef. + val companion: N = "companion" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2082c5ad600f..8855485ee5c8 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -391,7 +391,8 @@ object SymDenotations { if (is(Opaque)) { info match { case tp @ TypeAlias(alias) => - addAnnotation(Annotation.OpaqueAlias(alias)) + val companion = companionNamed(name.moduleClassName).sourceModule + addAnnotation(Annotation.OpaqueAlias(alias, companion)) info = TypeBounds(defn.NothingType, abstractRHS(alias)) setFlag(Deferred) case _ => @@ -968,10 +969,17 @@ object SymDenotations { */ final def companionModule(implicit ctx: Context): Symbol = if (is(Module)) sourceModule - else getAnnotation(defn.LinkedTypeAnnot) match { - case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol.sourceModule - case _ => NoSymbol - } + else if (is(Opaque)) + getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(Annotation.OpaqueAlias(_, ref)) => ref + case _ => NoSymbol + } + else + getAnnotation(defn.LinkedTypeAnnot) match { + case Some(Annotation.LinkedType(linked: TypeRef)) => + linked.symbol.sourceModule + case _ => NoSymbol + } private def companionType(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7e6e7c8763e8..9051c4bb91fd 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -749,16 +749,9 @@ class TreeUnpickler(reader: TastyReader, // The only case to check here is if `sym` is a root. In this case // `companion` might have been entered by the environment but it might // be missing from the Tasty file. So we check explicitly for that. - def isCodefined = - roots.contains(companion.denot) == seenRoots.contains(companion) - if (companion.exists && isCodefined) { - if (companion.isClass) - sym.registerCompanion(companion) - else if (sym.is(Module) && companion.is(Opaque)) { - sym.registerCompanion(companion) - companion.registerCompanion(sym) - } - } + def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) + + if (companion.canHaveCompanion && isCodefined) sym.registerCompanion(companion) TypeDef(readTemplate(localCtx)) } else { sym.info = TypeBounds.empty // needed to avoid cyclic references when unpicklin rhs, see i3816.scala diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index aaf3616cb596..1f63a093aa79 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -70,7 +70,7 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => case _ => if (sym.is(Opaque)) sym.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs)) => + case Some(Annotation.OpaqueAlias(rhs, _)) => val result = sym.copySymDenotation(info = TypeAlias(rhs)) result.removeAnnotation(defn.OpaqueAliasAnnot) result.resetFlag(Opaque) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5cd627e972ba..fbbc2c3242be 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -111,7 +111,7 @@ trait NamerContextOps { this: Context => if (ctx.owner.is(Module)) { val opaq = ctx.owner.companionOpaqueType opaq.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs)) => + case Some(Annotation.OpaqueAlias(rhs, _)) => localCtx = localCtx.setFreshGADTBounds localCtx.gadt.setBounds(opaq, TypeAlias(rhs)) case _ => From 4006366206401d41849cb6fbefa186d3e8c36cdf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 23 Feb 2018 09:29:57 +0100 Subject: [PATCH 16/33] Replace annotation extractor with access methods --- .../dotty/tools/dotc/core/Annotations.scala | 17 ---------- .../src/dotty/tools/dotc/core/StdNames.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 31 ++++++++++++++++--- .../tools/dotc/transform/FirstTransform.scala | 17 ++++------ .../src/dotty/tools/dotc/typer/Namer.scala | 9 +++--- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index fec280eef80b..18b47355470c 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -185,23 +185,6 @@ object Annotations { else None } - /** Extractor for opaque alias annotations */ - object OpaqueAlias { - def apply(tp: Type, companion: Symbol)(implicit ctx: Context): Annotation = { - val arg = if (companion.exists) RefinedType(tp, nme.companion, companion.termRef) else tp - Annotation(TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg))) - } - def unapply(ann: Annotation)(implicit ctx: Context): Option[(Type, Symbol)] = - if (ann.symbol == defn.OpaqueAliasAnnot) { - ann.tree.tpe match { - case AppliedType(_, RefinedType(tp, nme.companion, ref) :: Nil) => Some((tp, ref.termSymbol)) - case AppliedType(_, tp :: Nil) => Some((tp, NoSymbol)) - case _ => None - } - } - else None - } - /** Extractor for linked type annotations */ object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 54bbc139701a..6458a0676a9b 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -247,6 +247,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val DEFAULT_CASE: N = "defaultCase$" @@ -395,7 +396,6 @@ object StdNames { val classOf: N = "classOf" val clone_ : N = "clone" // val conforms : N = "conforms" // Dotty deviation: no special treatment of conforms, so the occurrence of the name here would cause to unintended implicit shadowing. Should find a less common name for it in Predef. - val companion: N = "companion" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 8855485ee5c8..16a083661762 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -390,9 +390,12 @@ object SymDenotations { } if (is(Opaque)) { info match { - case tp @ TypeAlias(alias) => + case TypeAlias(alias) => val companion = companionNamed(name.moduleClassName).sourceModule - addAnnotation(Annotation.OpaqueAlias(alias, companion)) + val arg = + if (companion.exists) RefinedType(alias, nme.COMPANION, companion.termRef) + else alias + addAnnotation(Annotation(tpd.TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg)))) info = TypeBounds(defn.NothingType, abstractRHS(alias)) setFlag(Deferred) case _ => @@ -971,8 +974,13 @@ object SymDenotations { if (is(Module)) sourceModule else if (is(Opaque)) getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(_, ref)) => ref - case _ => NoSymbol + case Some(ann) => + val AppliedType(_, arg :: Nil) = ann.tree.tpe + arg match { + case RefinedType(tp, _, ref) => ref.termSymbol + case _ => NoSymbol + } + case None => NoSymbol } else getAnnotation(defn.LinkedTypeAnnot) match { @@ -1052,6 +1060,21 @@ object SymDenotations { final def enclosingSubClass(implicit ctx: Context) = ctx.owner.ownersIterator.findSymbol(_.isSubClass(symbol)) + /** The alias of an opaque type */ + def opaqueAlias(implicit ctx: Context): Type = { + if (is(Opaque)) + getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(ann) => + val AppliedType(_, arg :: Nil) = ann.tree.tpe + arg match { + case RefinedType(tp, nme.COMPANION, _) => tp + case tp => tp + } + case None => NoType + } + else NoType + } + /** The non-private symbol whose name and type matches the type of this symbol * in the given class. * @param inClass The class containing the result symbol's definition diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 1f63a093aa79..b8e0819471fb 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -68,17 +68,12 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => sym.copySymDenotation(info = tp.derivedClassInfo(selfInfo = self.info)) .copyCaches(sym, ctx.phase.next) case _ => - if (sym.is(Opaque)) - sym.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs, _)) => - val result = sym.copySymDenotation(info = TypeAlias(rhs)) - result.removeAnnotation(defn.OpaqueAliasAnnot) - result.resetFlag(Opaque) - result.resetFlag(Deferred) - result - case _ => - sym - } + if (sym.is(Opaque)) { + val result = sym.copySymDenotation(info = TypeAlias(sym.opaqueAlias)) + result.removeAnnotation(defn.OpaqueAliasAnnot) + result.resetFlag(Opaque | Deferred) + result + } else sym } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fbbc2c3242be..b573d4087394 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -110,11 +110,10 @@ trait NamerContextOps { this: Context => } if (ctx.owner.is(Module)) { val opaq = ctx.owner.companionOpaqueType - opaq.getAnnotation(defn.OpaqueAliasAnnot) match { - case Some(Annotation.OpaqueAlias(rhs, _)) => - localCtx = localCtx.setFreshGADTBounds - localCtx.gadt.setBounds(opaq, TypeAlias(rhs)) - case _ => + val alias = opaq.opaqueAlias + if (alias.exists) { + localCtx = localCtx.setFreshGADTBounds + localCtx.gadt.setBounds(opaq, TypeAlias(alias)) } } localCtx From fa099ed58a4771867957e9517c03002ba4e9deb4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 10:34:28 +0100 Subject: [PATCH 17/33] Simplify companion scheme Use a field in ClassDenotation instead of an annotation --- .../dotty/tools/dotc/core/Annotations.scala | 15 -------- .../dotty/tools/dotc/core/Definitions.scala | 2 -- .../src/dotty/tools/dotc/core/Flags.scala | 4 ++- .../tools/dotc/core/SymDenotations.scala | 34 +++++++++++-------- .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../core/unpickleScala2/Scala2Unpickler.scala | 7 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 6 +--- .../src/dotty/tools/dotc/typer/Namer.scala | 8 ++--- 9 files changed, 31 insertions(+), 49 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 18b47355470c..2da8c95f0b71 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -173,21 +173,6 @@ object Annotations { else None } - /** A generic extractor for annotations carrying types */ - class TypeHintExtractor(cls: Context => ClassSymbol) { - def apply(tp: Type)(implicit ctx: Context): Annotation = - Annotation(TypeTree(cls(ctx).typeRef.appliedTo(tp))) - def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] = - if (ann.symbol == cls(ctx)) { - val AppliedType(tycon, arg :: Nil) = ann.tree.tpe - Some(arg) - } - else None - } - - /** Extractor for linked type annotations */ - object LinkedType extends TypeHintExtractor(implicit ctx => defn.LinkedTypeAnnot) - def makeSourceFile(path: String)(implicit ctx: Context) = apply(defn.SourceFileAnnot, Literal(Constant(path))) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 22eebd20684a..e2a4968226bc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -537,7 +537,6 @@ class Definitions { lazy val EqualsPatternClass = enterSpecialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType)) lazy val RepeatedParamClass = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) - lazy val LinkedTypeAnnot = enterSpecialPolyClass(tpnme.LINKED_TYPE, EmptyFlags, Seq(AnyType)) lazy val OpaqueAliasAnnot = enterSpecialPolyClass(tpnme.OPAQUE_ALIAS, EmptyFlags, Seq(AnyType)) // fundamental classes @@ -1173,7 +1172,6 @@ class Definitions { AnyKindClass, RepeatedParamClass, ByNameParamClass2x, - LinkedTypeAnnot, OpaqueAliasAnnot, AnyValClass, NullClass, diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index ed702f328558..c9e2f1926fb6 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -257,6 +257,8 @@ object Flags { /** An opqaue type */ final val Opaque = typeFlag(12, "opaque") + final val MutableOrOpaque = Mutable.toCommonFlags + /** Symbol is local to current class (i.e. private[this] or protected[this] * pre: Private or Protected are also set */ @@ -460,7 +462,7 @@ object Flags { /** Flags guaranteed to be set upon symbol creation */ final val FromStartFlags = Module | Package | Deferred | MethodOrHKCommon | Param | ParamAccessor.toCommonFlags | - Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | + Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | Erroneous | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 16a083661762..80c6873b5dca 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -960,11 +960,12 @@ object SymDenotations { final def enclosingPackageClass(implicit ctx: Context): Symbol = if (this is PackageClass) symbol else owner.enclosingPackageClass - /** Register target as a companion */ - def registerCompanion(target: Symbol)(implicit ctx: Context) = - if (exists && target.exists && !unforcedIsAbsent && !target.unforcedIsAbsent && - !myAnnotations.exists(_.symbol == defn.LinkedTypeAnnot)) - addAnnotation(Annotation.LinkedType(target.typeRef)) + /** Register target as a companion; overridden in ClassDenotation */ + def registerCompanion(target: Symbol)(implicit ctx: Context) = () + + /** The registered companion; overridden in ClassDenotation */ + def registeredCompanion(implicit ctx: Context): Symbol = NoSymbol + def registeredCompanion_=(c: Symbol): Unit = () /** The module object with the same (term-) name as this class or module class, * and which is also defined in the same scope and compilation unit. @@ -982,20 +983,12 @@ object SymDenotations { } case None => NoSymbol } - else - getAnnotation(defn.LinkedTypeAnnot) match { - case Some(Annotation.LinkedType(linked: TypeRef)) => - linked.symbol.sourceModule - case _ => NoSymbol - } + else registeredCompanion.sourceModule private def companionType(implicit ctx: Context): Symbol = if (is(Package)) NoSymbol else if (is(ModuleVal)) moduleClass.denot.companionType - else getAnnotation(defn.LinkedTypeAnnot) match { - case Some(Annotation.LinkedType(linked: TypeRef)) => linked.symbol - case _ => NoSymbol - } + else registeredCompanion /** The class with the same (type-) name as this module or module class, * and which is also defined in the same scope and compilation unit. @@ -1287,6 +1280,7 @@ object SymDenotations { val annotations1 = if (annotations != null) annotations else this.annotations val d = ctx.SymDenotation(symbol, owner, name, initFlags1, info1, privateWithin1) d.annotations = annotations1 + d.registeredCompanion = registeredCompanion d } @@ -1852,6 +1846,16 @@ object SymDenotations { .copyCaches(this, phase.next) .installAfter(phase) } + + private[this] var myCompanion: Symbol = NoSymbol + + /** Register companion class */ + override def registerCompanion(companion: Symbol)(implicit ctx: Context) = + if (companion.canHaveCompanion && !unforcedIsAbsent && !companion.unforcedIsAbsent) + myCompanion = companion + + override def registeredCompanion(implicit ctx: Context) = { ensureCompleted(); myCompanion } + override def registeredCompanion_=(c: Symbol) = { myCompanion = c } } /** The denotation of a package class. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0feddd48eeef..d3714e210395 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -621,7 +621,7 @@ class TreePickler(pickler: TastyPickler) { // See tests/pickling/i3149.scala case _ => val sym = ann.symbol - sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot || sym == defn.LinkedTypeAnnot + sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot // these are reconstituted automatically when unpickling } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9051c4bb91fd..ef092898efa3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -751,7 +751,7 @@ class TreeUnpickler(reader: TastyReader, // be missing from the Tasty file. So we check explicitly for that. def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) - if (companion.canHaveCompanion && isCodefined) sym.registerCompanion(companion) + if (companion.exists && isCodefined) sym.registerCompanion(companion) TypeDef(readTemplate(localCtx)) } else { sym.info = TypeBounds.empty // needed to avoid cyclic references when unpicklin rhs, see i3816.scala diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 83c0baf4acec..e1361d513bb1 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -121,14 +121,13 @@ object Scala2Unpickler { def registerCompanionPair(module: Symbol, claz: Symbol) = { module.registerCompanion(claz) - if (claz.isClass) claz.registerCompanion(module) + claz.registerCompanion(module) } - if (denot.flagsUNSAFE is Module) { + if (denot.flagsUNSAFE is Module) registerCompanionPair(denot.classSymbol, scalacCompanion) - } else { + else registerCompanionPair(scalacCompanion, denot.classSymbol) - } tempInfo.finalize(denot, normalizedParents) // install final info, except possibly for typeparams ordering denot.ensureTypeParamsInCorrectOrder() diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ce00faee2d16..48bc992f8a47 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -694,11 +694,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(annotations.map(annotText), " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } - protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = - annots filter { - case tpt: TypeTree[_] => tpt.typeOpt.typeSymbol != defn.LinkedTypeAnnot - case _ => true - } + protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = annots def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index b573d4087394..fe605981317f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -620,11 +620,9 @@ class Namer { typer: Typer => def createLinks(classTree: TypeDef, moduleTree: TypeDef)(implicit ctx: Context) = { val claz = ctx.effectiveScope.lookup(classTree.name) val modl = ctx.effectiveScope.lookup(moduleTree.name) - if (modl.isClass && claz.canHaveCompanion) { - modl.registerCompanion(claz) - claz.registerCompanion(modl) - } - } + modl.registerCompanion(claz) + claz.registerCompanion(modl) + } def createCompanionLinks(implicit ctx: Context): Unit = { val classDef = mutable.Map[TypeName, TypeDef]() From 55cd70f7ba9f7b1df078971868349011d4132a62 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 24 Feb 2018 17:58:01 +0100 Subject: [PATCH 18/33] add test --- tests/neg/opaque.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala index efb06e8582c9..3b675f563813 100644 --- a/tests/neg/opaque.scala +++ b/tests/neg/opaque.scala @@ -1,4 +1,5 @@ object opaquetypes { + opaque val x: Int = 1 // error opaque class Foo // error @@ -7,6 +8,8 @@ object opaquetypes { opaque type U <: String // error + opaque type Fix[F[_]] = F[Fix[F]] // error: cyclic + opaque type O = String val s: O = "" // error From 6161baf1fb67d84f57ee86d9a9398fcbc5a16b43 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 9 Mar 2018 17:24:21 +0100 Subject: [PATCH 19/33] Follow GADT bounds when computing members When computing the member denotation of a selection of a TypeRef `T`, if the normal scheme fails and `T` has GADT bounds, compute the member in the upper bound instead. This is needed to make the opaque-probability test work. Add this test, as well as some others coming from SIP 35. --- .../src/dotty/tools/dotc/core/Types.scala | 19 +++- .../dotty/tools/dotc/typer/ProtoTypes.scala | 7 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 8 +- .../src/dotty/tools/dotc/typer/Typer.scala | 7 +- tests/pending/pos/opaque-recursive.scala | 16 ++++ tests/pos/opaque-digits.scala | 22 +++++ tests/pos/opaque-goups.scala | 95 +++++++++++++++++++ tests/pos/opaque-nullable.scala | 26 +++++ tests/pos/opaque-propability.scala | 43 +++++++++ 9 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 tests/pending/pos/opaque-recursive.scala create mode 100644 tests/pos/opaque-digits.scala create mode 100644 tests/pos/opaque-goups.scala create mode 100644 tests/pos/opaque-nullable.scala create mode 100644 tests/pos/opaque-propability.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2f3e13c89847..c3e50d8ff0ba 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -263,7 +263,7 @@ object Types { } /** Is some part of this type produced as a repair for an error? */ - final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) + def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) /** Does the type carry an annotation that is an instance of `cls`? */ @tailrec final def hasAnnotation(cls: ClassSymbol)(implicit ctx: Context): Boolean = stripTypeVar match { @@ -1290,6 +1290,23 @@ object Types { */ def deepenProto(implicit ctx: Context): Type = this + /** If this is a TypeRef or an Application of a GADT-bound type, replace the + * GADT reference by its upper GADT bound. Otherwise NoType. + */ + def followGADT(implicit ctx: Context): Type = widenDealias match { + case site: TypeRef if site.symbol.is(Opaque) => + ctx.gadt.bounds(site.symbol) match { + case TypeBounds(_, hi) => hi + case _ => NoType + } + case AppliedType(tycon, args) => + val tycon1 = tycon.followGADT + if (tycon1.exists) tycon1.appliedTo(args) + else NoType + case _ => + NoType + } + // ----- Substitutions ----------------------------------------------------- /** Substitute all types that refer in their symbol attribute to diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5aa4a2fbfd31..26d6176d4551 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -6,6 +6,7 @@ import core._ import ast._ import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps._, Symbols._ import NameKinds.DepParamName +import SymDenotations.NoDenotation import Trees._ import Constants._ import Scopes._ @@ -128,8 +129,9 @@ object ProtoTypes { memberProto.isRef(defn.UnitClass) || compat.normalizedCompatible(NamedType(tp1, name, m), memberProto) // Note: can't use `m.info` here because if `m` is a method, `m.info` - // loses knowledge about `m`'s default arguments. + // loses knowledge about `m`'s default arguments. || mbr match { // hasAltWith inlined for performance + case NoDenotation => tp1.exists && isMatchedBy(tp1.followGADT) case mbr: SingleDenotation => mbr.exists && qualifies(mbr) case _ => mbr hasAltWith qualifies } @@ -318,6 +320,9 @@ object ProtoTypes { def isDropped: Boolean = toDrop + override def isErroneous(implicit ctx: Context): Boolean = + myTypedArgs.tpes.exists(_.widen.isErroneous) + override def toString = s"FunProto(${args mkString ","} => $resultType)" def map(tm: TypeMap)(implicit ctx: Context): FunProto = diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 61502cddf482..b3dd9cfbd9e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -236,8 +236,12 @@ trait TypeAssigner { if (reallyExists(mbr)) site.select(name, mbr) else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { TryDynamicCallType - } else { - if (site.isErroneous || name.toTermName == nme.ERROR) UnspecifiedErrorType + } + else { + val site1 = site.followGADT + if (site1.exists) selectionType(site1, name, pos) + else if (site.isErroneous || name.toTermName == nme.ERROR) + UnspecifiedErrorType else { def kind = if (name.isTypeName) "type" else "value" def addendum = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 431721dc44e6..a8c627241595 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2114,8 +2114,11 @@ class Typer extends Namer noMatches } case alts => - val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) - errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType) + else { + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + } } } diff --git a/tests/pending/pos/opaque-recursive.scala b/tests/pending/pos/opaque-recursive.scala new file mode 100644 index 000000000000..0111fce09bef --- /dev/null +++ b/tests/pending/pos/opaque-recursive.scala @@ -0,0 +1,16 @@ +object opaquetypes { + + opaque type Fix[F[_]] = F[Fix2[F]] + + opaque type Fix2[F[_]] = Fix[F] + + object Fix { + def unfold[F[_]](x: Fix[F]): F[Fix] + } + + object Fix2 { + def unfold[F[_]](x: Fix2[F]: Fix[F] = x + def fold[F[_]](x: Fix[F]: Fix2[F] = x + } + +} diff --git a/tests/pos/opaque-digits.scala b/tests/pos/opaque-digits.scala new file mode 100644 index 000000000000..29eed387dc2e --- /dev/null +++ b/tests/pos/opaque-digits.scala @@ -0,0 +1,22 @@ +object pkg { + + import Character.{isAlphabetic, isDigit} + + class Alphabetic private[pkg] (val value: String) extends AnyVal + + object Alphabetic { + def fromString(s: String): Option[Alphabetic] = + if (s.forall(isAlphabetic(_))) Some(new Alphabetic(s)) + else None + } + + opaque type Digits = String + + object Digits { + def fromString(s: String): Option[Digits] = + if (s.forall(isDigit(_))) Some(s) + else None + + def asString(d: Digits): String = d + } +} \ No newline at end of file diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-goups.scala new file mode 100644 index 000000000000..4391d756494b --- /dev/null +++ b/tests/pos/opaque-goups.scala @@ -0,0 +1,95 @@ +package object groups { + trait Semigroup[A] { + def combine(x: A, y: A): A + } + + object Semigroup { + def instance[A](f: (A, A) => A): Semigroup[A] = + new Semigroup[A] { + def combine(x: A, y: A): A = f(x, y) + } + } + + type Id[A] = A + + trait Wrapping[F[_]] { + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](ga: G[F[A]]): G[A] + } + + abstract class Wrapper[F[_]] { self => + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](gfa: G[F[A]]): G[A] + + final def apply[A](a: A): F[A] = wraps[Id, A](a) + + implicit object WrapperWrapping extends Wrapping[F] { + def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga) + def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga) + } + } + + opaque type First[A] = A + object First extends Wrapper[First] { + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga + def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa + implicit def firstSemigroup[A]: Semigroup[First[A]] = + Semigroup.instance((x, y) => x) + } + + opaque type Last[A] = A + object Last extends Wrapper[Last] { + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } + + opaque type Min[A] = A + object Min extends Wrapper[Min] { + def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga + def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa + implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] = + Semigroup.instance(o.min) + } + + opaque type Max[A] = A + object Max extends Wrapper[Max] { + def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga + def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa + implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] = + Semigroup.instance(o.max) + } + + opaque type Plus[A] = A + object Plus extends Wrapper[Plus] { + def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga + def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa + implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] = + Semigroup.instance(n.plus) + } + + opaque type Times[A] = A + object Times extends Wrapper[Times] { + def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga + def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa + implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] = + Semigroup.instance(n.times) + } + + opaque type Reversed[A] = A + object Reversed extends Wrapper[Reversed] { + def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga + def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa + implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] = + o.reverse + } + + opaque type Unordered[A] = A + object Unordered extends Wrapper[Unordered] { + def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga + def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa + implicit def unorderedOrdering[A]: Ordering[Unordered[A]] = + Ordering.by(_ => ()) + } +} \ No newline at end of file diff --git a/tests/pos/opaque-nullable.scala b/tests/pos/opaque-nullable.scala new file mode 100644 index 000000000000..ce32d7c1cfed --- /dev/null +++ b/tests/pos/opaque-nullable.scala @@ -0,0 +1,26 @@ +object nullable { + opaque type Nullable[A >: Null <: AnyRef] = A + + object Nullable { + def apply[A >: Null <: AnyRef](a: A): Nullable[A] = a + + implicit class NullableOps[A >: Null <: AnyRef](na: Nullable[A]) { + def exists(p: A => Boolean): Boolean = + na != null && p(na) + def filter(p: A => Boolean): Nullable[A] = + if (na != null && p(na)) na else null + def flatMap[B >: Null <: AnyRef](f: A => Nullable[B]): Nullable[B] = + if (na == null) null else f(na) + def forall(p: A => Boolean): Boolean = + na == null || p(na) + def getOrElse(a: => A): A = + if (na == null) a else na + def map[B >: Null <: AnyRef](f: A => B): Nullable[B] = + if (na == null) null else f(na) + def orElse(na2: => Nullable[A]): Nullable[A] = + if (na == null) na2 else na + def toOption: Option[A] = + Option(na) + } + } +} \ No newline at end of file diff --git a/tests/pos/opaque-propability.scala b/tests/pos/opaque-propability.scala new file mode 100644 index 000000000000..68048949ea93 --- /dev/null +++ b/tests/pos/opaque-propability.scala @@ -0,0 +1,43 @@ +object prob { + opaque type Probability = Double + + object Probability { + def apply(n: Double): Option[Probability] = + if (0.0 <= n && n <= 1.0) Some(n) else None + + def unsafe(p: Double): Probability = { + require(0.0 <= p && p <= 1.0, s"probabilities lie in [0, 1] (got $p)") + p + } + + def asDouble(p: Probability): Double = p + + val Never: Probability = 0.0 + val CoinToss: Probability = 0.5 + val Certain: Probability = 1.0 + + implicit val ordering: Ordering[Probability] = + implicitly[Ordering[Double]] + + implicit class ProbabilityOps(p1: Probability) extends AnyVal { + def unary_~ : Probability = Certain - p1 + def &(p2: Probability): Probability = p1 * p2 + def |(p2: Probability): Probability = p1 + p2 - (p1 * p2) + + def isImpossible: Boolean = p1 == Never + def isCertain: Boolean = p1 == Certain + + import scala.util.Random + + def sample(r: Random = Random): Boolean = r.nextDouble <= p1 + def toDouble: Double = p1 + } + + val caughtTrain = Probability.unsafe(0.3) + val missedTrain = ~caughtTrain + val caughtCab = Probability.CoinToss + val arrived = caughtTrain | (missedTrain & caughtCab) + + println((1 to 5).map(_ => arrived.sample()).toList) + } +} From 6fee4bb5e9c80ec6fba013edf3059d0ece438c6b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 11:13:31 +0100 Subject: [PATCH 20/33] Consider GADT bounds for findMember and underlying The `findMember` and `underlying` methods on `TermRef` now take GADT bounds into account. This replaces the previous special case in `secectionType`. The previous case did always pass -Ycheck. Currently, the treatment is restricted to TypeRefs of opaque types. We should extend that to all GADT-bound TypeRefs. The question is how this will affect performance. --- .../src/dotty/tools/dotc/core/Types.scala | 18 ++++++++--- .../dotty/tools/dotc/typer/TypeAssigner.scala | 30 ++++++++----------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c3e50d8ff0ba..ecaf90556d8d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -509,8 +509,12 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => d.findMember(name, pre, excluded) - case d => go(d.info) + case d: ClassDenotation => + d.findMember(name, pre, excluded) + case d => + val mbr = go(d.info) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) } case tp: AppliedType => tp.tycon match { @@ -521,7 +525,7 @@ object Types { case _ => go(tp.superType) } - case tp: ThisType => // ??? inline + case tp: ThisType => goThis(tp) case tp: RefinedType => if (name eq tp.refinedName) goRefined(tp) else go(tp.parent) @@ -2133,7 +2137,13 @@ object Types { override def designator = myDesignator override protected def designator_=(d: Designator) = myDesignator = d - override def underlying(implicit ctx: Context): Type = info + override def underlying(implicit ctx: Context): Type = { + if (symbol.is(Opaque)) { + val gadtBounds = ctx.gadt.bounds(symbol) + if (gadtBounds != null) return gadtBounds + } + info + } } final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index b3dd9cfbd9e4..081c8c2130ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -233,25 +233,21 @@ trait TypeAssigner { */ def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { val mbr = site.member(name) - if (reallyExists(mbr)) site.select(name, mbr) - else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { + if (reallyExists(mbr)) + site.select(name, mbr) + else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) TryDynamicCallType - } + else if (site.isErroneous || name.toTermName == nme.ERROR) + UnspecifiedErrorType else { - val site1 = site.followGADT - if (site1.exists) selectionType(site1, name, pos) - else if (site.isErroneous || name.toTermName == nme.ERROR) - UnspecifiedErrorType - else { - def kind = if (name.isTypeName) "type" else "value" - def addendum = - if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" - else "" - errorType( - if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" - else NotAMember(site, name, kind), - pos) - } + def kind = if (name.isTypeName) "type" else "value" + def addendum = + if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" + else "" + errorType( + if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" + else NotAMember(site, name, kind), + pos) } } From ea65c25dbc69543236e245eb7594bbc86338625e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 12 Mar 2018 11:14:31 +0100 Subject: [PATCH 21/33] Extend opaque companion context to inlined code The GADT bounds set in an opaque companion also need to be established for any inlined code coming from the companion. Test case in opaque-immutable-array.scala. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 7 ++- .../src/dotty/tools/dotc/typer/Namer.scala | 30 ++++++++----- ...opaque-goups.scala => opaque-groups.scala} | 0 tests/pos/opaque-immutable-array.scala | 43 +++++++++++++++++++ 4 files changed, 68 insertions(+), 12 deletions(-) rename tests/pos/{opaque-goups.scala => opaque-groups.scala} (100%) create mode 100644 tests/pos/opaque-immutable-array.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a3be000dbbc2..e4f1a96e63a4 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1017,8 +1017,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] - override def inlineContext(call: Tree)(implicit ctx: Context): Context = - ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + override def inlineContext(call: Tree)(implicit ctx: Context): Context = { + val ictx = ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + def stopAt(owner: Symbol) = owner.is(Package) || ctx.owner.isContainedIn(owner) + (ictx /: call.symbol.ownersIterator.takeWhile(!stopAt(_)))(ctx.handleOpaqueCompanion) + } /** All enclosing calls that are currently inlined, from innermost to outermost */ def enclosingInlineds(implicit ctx: Context): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fe605981317f..dae5f9b61f49 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -101,22 +101,32 @@ trait NamerContextOps { this: Context => if (owner.exists) freshCtx.setOwner(owner) else freshCtx } + /** If `owner` is a companion object of an opaque type, record the alias + * in the GADT bounds of `freshCtx. + */ + def handleOpaqueCompanion(freshCtx: FreshContext, owner: Symbol): FreshContext = { + if (owner.is(Module)) { + val opaq = owner.companionOpaqueType + val alias = opaq.opaqueAlias + if (alias.exists) { + println(i"set GADT bounds of $opaq : $alias") + val result = freshCtx.setFreshGADTBounds + result.gadt.setBounds(opaq, TypeAlias(alias)) + result + } + else freshCtx + } + else freshCtx + } + /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - var localCtx: FreshContext = ctx.fresh.setNewScope + val localCtx: FreshContext = ctx.fresh.setNewScope selfInfo match { case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => localCtx.scope.openForMutations.enter(sym) case _ => } - if (ctx.owner.is(Module)) { - val opaq = ctx.owner.companionOpaqueType - val alias = opaq.opaqueAlias - if (alias.exists) { - localCtx = localCtx.setFreshGADTBounds - localCtx.gadt.setBounds(opaq, TypeAlias(alias)) - } - } - localCtx + handleOpaqueCompanion(localCtx, ctx.owner) } def packageContext(tree: untpd.PackageDef, pkg: Symbol): Context = diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-groups.scala similarity index 100% rename from tests/pos/opaque-goups.scala rename to tests/pos/opaque-groups.scala diff --git a/tests/pos/opaque-immutable-array.scala b/tests/pos/opaque-immutable-array.scala new file mode 100644 index 000000000000..ad657ff17a10 --- /dev/null +++ b/tests/pos/opaque-immutable-array.scala @@ -0,0 +1,43 @@ +object ia { + + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + object IArray { + @inline final def initialize[A](body: => Array[A]): IArray[A] = body + + @inline final def size[A](ia: IArray[A]): Int = ia.length + @inline final def get[A](ia: IArray[A], i: Int): A = ia(i) + + // return a sorted copy of the array + def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = { + val arr = Arrays.copyOf(ia, ia.length) + scala.util.Sorting.quickSort(arr) + arr + } + + // use a standard java method to search a sorted IArray. + // (note that this doesn't mutate the array). + def binarySearch(ia: IArray[Long], elem: Long): Int = + Arrays.binarySearch(ia, elem) + } + + // same as IArray.binarySearch but implemented by-hand. + // + // given a sorted IArray, returns index of `elem`, + // or a negative value if not found. + def binaryIndexOf(ia: IArray[Long], elem: Long): Int = { + var lower: Int = 0 + var upper: Int = IArray.size(ia) + while (lower <= upper) { + val middle = (lower + upper) >>> 1 + val n = IArray.get(ia, middle) + + if (n == elem) return middle + else if (n < elem) lower = middle + 1 + else upper = middle - 1 + } + -lower - 1 + } +} \ No newline at end of file From e50e66944100626030a83d11c6609be260f9cbb2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 21:11:36 +0100 Subject: [PATCH 22/33] Remove stray println --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index dae5f9b61f49..089b359ada03 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -109,7 +109,6 @@ trait NamerContextOps { this: Context => val opaq = owner.companionOpaqueType val alias = opaq.opaqueAlias if (alias.exists) { - println(i"set GADT bounds of $opaq : $alias") val result = freshCtx.setFreshGADTBounds result.gadt.setBounds(opaq, TypeAlias(alias)) result From e3e58eb26ee69bd90e4df4c7d800ba18aa5c9254 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 18:03:28 +0100 Subject: [PATCH 23/33] Disable failing FromTasty tests --- compiler/test/dotty/tools/dotc/FromTastyTests.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 75f2ed603242..695e6878a0f5 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -28,6 +28,11 @@ class FromTastyTests extends ParallelTesting { implicit val testGroup: TestGroup = TestGroup("posTestFromTasty") val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( + + "opaque.scala", + "opaque-propability.scala", + "opaque-immutable-array.scala", + "macro-deprecate-dont-touch-backquotedidents.scala", "t247.scala", From 252f04ae60d29b1bf8778f6e91ada00d5a5cd7ea Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 14:04:41 +0100 Subject: [PATCH 24/33] Fix ctx of opaque type companion --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 3 +++ compiler/test/dotty/tools/dotc/FromTastyTests.scala | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 29096e44bc38..a755f9130e1c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1244,6 +1244,8 @@ object Trees { Stats.record("TreeAccumulator.foldOver total") def localCtx = if (tree.hasType && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx + def templateCtx = + ctx.handleOpaqueCompanion(ctx.fresh, ctx.owner) tree match { case Ident(name) => x @@ -1320,6 +1322,7 @@ object Trees { implicit val ctx = localCtx this(x, rhs) case tree @ Template(constr, parents, self, _) => + implicit val ctx = templateCtx this(this(this(this(x, constr), parents), self), tree.body) case Import(expr, selectors) => this(x, expr) diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 695e6878a0f5..d199a80243c3 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -29,8 +29,6 @@ class FromTastyTests extends ParallelTesting { val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( - "opaque.scala", - "opaque-propability.scala", "opaque-immutable-array.scala", "macro-deprecate-dont-touch-backquotedidents.scala", From c8ff1d66e1d39099c19c7e0777a1385c5754ffff Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 15:35:10 +0100 Subject: [PATCH 25/33] Fix ctx and call of inlined opaque extension methods --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 5 +++-- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 5 ++++- compiler/test/dotty/tools/dotc/FromTastyTests.scala | 2 -- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index ef092898efa3..c4ab9ca41efb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -986,7 +986,7 @@ class TreeUnpickler(reader: TastyReader, def readLengthTerm(): Tree = { val end = readEnd() - def readBlock(mkTree: (List[Tree], Tree) => Tree): Tree = { + def readBlock(mkTree: (List[Tree], Tree) => Tree)(implicit ctx: Context): Tree = { val exprReader = fork skipTree() val stats = readStats(ctx.owner, end) @@ -1015,7 +1015,8 @@ class TreeUnpickler(reader: TastyReader, readBlock(Block) case INLINED => val call = readTerm() - readBlock((defs, expr) => Inlined(call, defs.asInstanceOf[List[MemberDef]], expr)) + val inlineCtx = tpd.inlineContext(call) + readBlock((defs, expr) => Inlined(call, defs.asInstanceOf[List[MemberDef]], expr))(inlineCtx) case IF => If(readTerm(), readTerm(), readTerm()) case LAMBDA => diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 103afdad960a..c241a50fa563 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -238,9 +238,12 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase // In the case of macros we keep the call to be able to reconstruct the parameters that // are passed to the macro. This same simplification is applied in ReifiedQuotes when the // macro splices are evaluated. + def symTrace = + if (call.symbol.owner.companionOpaqueType.exists) call.symbol.owner + else call.symbol.topLevelClass val callTrace = if (call.symbol.is(Macro)) call - else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + else Ident(symTrace.typeRef).withPos(call.pos) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index d199a80243c3..00cf744b10e8 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -29,8 +29,6 @@ class FromTastyTests extends ParallelTesting { val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( - "opaque-immutable-array.scala", - "macro-deprecate-dont-touch-backquotedidents.scala", "t247.scala", From cf740ba4c7e379de0f1dd60f4e7920d0c2229c6a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 14:01:09 +0200 Subject: [PATCH 26/33] Fix rebase breakage --- .../src/dotty/tools/dotc/core/Types.scala | 13 +-- .../tools/dotc/transform/FirstTransform.scala | 2 - .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- tests/pos/opaque-goups.scala | 95 +++++++++++++++++++ 4 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 tests/pos/opaque-goups.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ecaf90556d8d..ef5b321a8fa9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -509,12 +509,8 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => - d.findMember(name, pre, excluded) - case d => - val mbr = go(d.info) - if (mbr.exists) mbr - else followGADT.findMember(name, pre, excluded) + case d: ClassDenotation => d.findMember(name, pre, excluded) + case d => goTypeRef(d) } case tp: AppliedType => tp.tycon match { @@ -554,6 +550,11 @@ object Types { case _ => NoDenotation } + def goTypeRef(d: Denotation) = { + val mbr = go(d.info) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) + } def goRec(tp: RecType) = if (tp.parent == null) NoDenotation else { diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index b8e0819471fb..6671e81c0e81 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -77,8 +77,6 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => else sym } - override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 26d6176d4551..bd2982dbaf72 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -131,7 +131,9 @@ object ProtoTypes { // Note: can't use `m.info` here because if `m` is a method, `m.info` // loses knowledge about `m`'s default arguments. || mbr match { // hasAltWith inlined for performance - case NoDenotation => tp1.exists && isMatchedBy(tp1.followGADT) + case NoDenotation => + val tp2 = tp1.followGADT + tp2.exists && isMatchedBy(tp2) case mbr: SingleDenotation => mbr.exists && qualifies(mbr) case _ => mbr hasAltWith qualifies } diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-goups.scala new file mode 100644 index 000000000000..4391d756494b --- /dev/null +++ b/tests/pos/opaque-goups.scala @@ -0,0 +1,95 @@ +package object groups { + trait Semigroup[A] { + def combine(x: A, y: A): A + } + + object Semigroup { + def instance[A](f: (A, A) => A): Semigroup[A] = + new Semigroup[A] { + def combine(x: A, y: A): A = f(x, y) + } + } + + type Id[A] = A + + trait Wrapping[F[_]] { + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](ga: G[F[A]]): G[A] + } + + abstract class Wrapper[F[_]] { self => + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](gfa: G[F[A]]): G[A] + + final def apply[A](a: A): F[A] = wraps[Id, A](a) + + implicit object WrapperWrapping extends Wrapping[F] { + def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga) + def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga) + } + } + + opaque type First[A] = A + object First extends Wrapper[First] { + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga + def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa + implicit def firstSemigroup[A]: Semigroup[First[A]] = + Semigroup.instance((x, y) => x) + } + + opaque type Last[A] = A + object Last extends Wrapper[Last] { + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } + + opaque type Min[A] = A + object Min extends Wrapper[Min] { + def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga + def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa + implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] = + Semigroup.instance(o.min) + } + + opaque type Max[A] = A + object Max extends Wrapper[Max] { + def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga + def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa + implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] = + Semigroup.instance(o.max) + } + + opaque type Plus[A] = A + object Plus extends Wrapper[Plus] { + def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga + def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa + implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] = + Semigroup.instance(n.plus) + } + + opaque type Times[A] = A + object Times extends Wrapper[Times] { + def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga + def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa + implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] = + Semigroup.instance(n.times) + } + + opaque type Reversed[A] = A + object Reversed extends Wrapper[Reversed] { + def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga + def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa + implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] = + o.reverse + } + + opaque type Unordered[A] = A + object Unordered extends Wrapper[Unordered] { + def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga + def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa + implicit def unorderedOrdering[A]: Ordering[Unordered[A]] = + Ordering.by(_ => ()) + } +} \ No newline at end of file From b15866639048500f6d53e0e0c1cb0d917072b599 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 15:56:28 +0200 Subject: [PATCH 27/33] Fix modifier printing --- .../src/dotty/tools/dotc/core/Flags.scala | 17 ++++++---- .../dotc/printing/DecompilerPrinter.scala | 6 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 31 +++++++++++-------- .../tools/dottydoc/model/factories.scala | 2 +- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index c9e2f1926fb6..ffaf821528e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -444,15 +444,20 @@ object Flags { // --------- Combined Flag Sets and Conjunctions ---------------------- /** Flags representing source modifiers */ - final val SourceModifierFlags = - commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased, Opaque) + private val CommonSourceModifierFlags = + commonFlags(Private, Protected, Final, Case, Implicit, Override, JavaStatic) + + final val TypeSourceModifierFlags = + CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque + + final val TermSourceModifierFlags = + CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local - // | Mutable is subsumed by commonFlags(Opaque) from SourceModifierFlags - // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags + TypeSourceModifierFlags.toCommonFlags | + TermSourceModifierFlags.toCommonFlags | + commonFlags(Module, Param, Synthetic, Package, Local, Mutable, Trait) assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index c590adf2206e..a398ab652aa5 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -35,8 +35,8 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def templateText(tree: TypeDef, impl: Template): Text = { val decl = - if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) - else modText(tree.mods &~ (Final | Module), keywordStr("object")) + if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) + else modText(tree.mods &~ (Final | Module), keywordStr("object"), isType = false) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ "" } @@ -44,7 +44,7 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { import untpd.{modsDeco => _, _} dclTextOr(tree) { val printLambda = tree.symbol.isAnonymousFunction - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) provided (!printLambda) + val prefix = modText(tree.mods, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) provided (!printLambda) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt).provided(!printLambda) ~ optText(tree.rhs)((if (printLambda) " => " else " = ") ~ _) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48bc992f8a47..ee9a1df5921c 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -75,7 +75,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override protected def recursionLimitExceeded() = {} - protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags + protected def PrintableFlags(isType: Boolean) = { + if (isType) TypeSourceModifierFlags | Module | Local + else TermSourceModifierFlags | Label | Module | Local + }.toCommonFlags override def nameString(name: Name): String = if (ctx.settings.YdebugNames.value) name.debugString else name.toString @@ -403,7 +406,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree @ TypeDef(name, rhs) => def typeDefText(tparamsText: => Text, rhsText: => Text) = dclTextOr(tree) { - modText(tree.mods, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ + modText(tree.mods, keywordStr("type"), isType = true) ~~ + (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { @@ -441,7 +445,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { - modText(tree.mods, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) + modText(tree.mods, keywordStr("object"), isType = false) ~~ + nameIdText(tree) ~ toTextTemplate(impl) } case SymbolLit(str) => "'" + str @@ -498,8 +503,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { t ~ cxBoundToText(cxb) } case PatDef(mods, pats, tpt, rhs) => - modText(mods, keywordStr("val")) ~~ toText(pats, ", ") ~ optAscription(tpt) ~ - optText(rhs)(" = " ~ _) + modText(mods, keywordStr("val"), isType = false) ~~ + toText(pats, ", ") ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) case ParsedTry(expr, handler, finalizer) => changePrec(GlobalPrec) { keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _) @@ -606,7 +611,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ + modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } } @@ -615,7 +620,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + val prefix = modText(tree.mods, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ optText(tree.rhs)(" = " ~ _) @@ -630,7 +635,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val prefix: Text = if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt else { - var modsText = modText(constr.mods, "") + var modsText = modText(constr.mods, "", isType = false) if (!modsText.isEmpty) modsText = " " ~ modsText if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } @@ -658,7 +663,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def templateText(tree: TypeDef, impl: Template): Text = { - val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") } @@ -681,12 +686,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + protected def modText(mods: untpd.Modifiers, kw: String, isType: Boolean): Text = { // DD val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param var flagMask = if (ctx.settings.YdebugFlags.value) AnyFlags - else if (suppressKw) PrintableFlags &~ Private - else PrintableFlags + else if (suppressKw) PrintableFlags(isType) &~ Private + else PrintableFlags(isType) if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes val flags = mods.flags & flagMask val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) @@ -755,7 +760,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else { var flags = sym.flagsUNSAFE if (flags is TypeParam) flags = flags &~ Protected - Text((flags & PrintableFlags).flagStrings map (flag => stringToText(keywordStr(flag))), " ") + Text((flags & PrintableFlags(sym.isType)).flagStrings map (flag => stringToText(keywordStr(flag))), " ") } override def toText(denot: Denotation): Text = denot match { diff --git a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala index 66f42eb6e740..a9bcf8d738eb 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala @@ -26,7 +26,7 @@ object factories { type TypeTree = dotty.tools.dotc.ast.Trees.Tree[Type] def flags(t: Tree)(implicit ctx: Context): List[String] = - (t.symbol.flags & SourceModifierFlags) + (t.symbol.flags & (if (t.symbol.isType) TypeSourceModifierFlags else TermSourceModifierFlags)) .flagStrings.toList .filter(_ != "") .filter(_ != "interface") From 22a71672f402b89c0c5eb5e7ef4dfa8183814802 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 15:47:22 +0100 Subject: [PATCH 28/33] Schema for named extensions Following suggestions by @sjrd, a worked out scheme to have named extensions, using extension for { ... } syntax. --- docs/docs/internals/syntax.md | 14 +- .../reference/extend/extension-methods.md | 146 ++++++++++++++++++ .../reference/extend/instance-declarations.md | 62 ++++++++ docs/docs/reference/extend/translation.md | 85 ++++++++++ 4 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 docs/docs/reference/extend/extension-methods.md create mode 100644 docs/docs/reference/extend/instance-declarations.md create mode 100644 docs/docs/reference/extend/translation.md diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 3c9d9ef1c6c7..2a16fc06e323 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -330,12 +330,18 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef -ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) +ClassDef ::= id ClassConstr [TemplateClause] ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] -ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor -EnumDef ::= id ClassConstr [‘extends’ [ConstrApps]] EnumBody EnumDef(mods, name, tparams, template) -TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] +ObjectDef ::= id [TemplateClause] ModuleDef(mods, name, template) // no constructor +EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody TypeDef(mods, name, template) +Extension ::= 'extension' id [ExtensionParams] Extension(name, templ) + 'for' Type ExtensionClause +ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] +ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + +TemplateClause ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args) diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md new file mode 100644 index 000000000000..1790f89b6e45 --- /dev/null +++ b/docs/docs/reference/extend/extension-methods.md @@ -0,0 +1,146 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +extension CircleOps for Circle { + def circumference: Double = this.radius * math.Pi * 2 +} +``` + +The extension adds a method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Meaning of `this` + +Inside an extension method, the name `this` stands for the receiver on which the +method is applied when it is invoked. E.g. in the application of `circle.circumference`, +the `this` in the body of `circumference` refers to `circle`. As usual, `this` can be elided, so we could also have defined `CircleOps` like this: + +```scala +extension CircleOps for Circle { + def circumference: Double = radius * math.Pi * 2 +} +``` + +### Scope of Extensions + +Extensions can appear anywhere in a program; there is no need to co-define them with the types they extend. Extension methods are available wherever their defining extension is in scope. Extensions can be inherited or imported like normal definitions. + +### Extended Types + +An extension can add methods to arbitrary types. For instance, the following +clause adds a `longestStrings` extension method to a `Seq[String]`: + +```scala +extension StringOps for Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = map(_.length).max + filter(_.length == maxLength) + } +} +``` + +### Generic Extensions + +The previous example extended a specific instance of a generic type. It is also possible +to extend a generic type by adding type parameters to an extension: + +```scala +extension ListOps[T] for List[T] { + def second: T = tail.head +} +``` + +or: + + +```scala +extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +### Bounded Generic Extensions + +It is possible to use bounds for the type parameters of an extension. But subtype and context bounds are supported: + +```scala +extension ShapeListOps[T <: Shape] for List[T] { + def totalArea = map(_.area).sum +} + +extension OrdSeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 +} +``` + +### Implicit Parameters for Type Patterns + +The standard translation of context bounds expands the bound in the last example to an implicit _evidence_ parameter of type `math.Ordering[T]`. It is also possible to give evidence parameters explicitly. The following example is equivalent to the previous one: + +```scala +extension OrdSeqOps[T](implicit ev: math.Ordering[T]) for Seq[T] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +There can be only one parameter clause following a type pattern and it must be implicit. As usual, one can combine context bounds and implicit evidence parameters. + +### Toplevel Type Variables + +A type pattern consisting of a top-level typevariable introduces a fully generic extension. For instance, the following extension introduces `x ~ y` as an alias +for `(x, y)`: + +```scala +extension InfixPair[T] for T { + def ~ [U](that: U) = (this, that) +} +``` + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + extenson Ensuring[T] for T { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } +} +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md new file mode 100644 index 000000000000..2ab5d3883c11 --- /dev/null +++ b/docs/docs/reference/extend/instance-declarations.md @@ -0,0 +1,62 @@ +--- +layout: doc-page +title: "Instance Declarations" +--- + +In addition to adding methods, an extension can also implement traits. Extensions implementing traits are also called _instance declarations_. For example, + +```scala +trait HasArea { + def area: Double +} + +extension CircleHasArea for Circle : HasArea { + def area = this.radius * this.radius * math.Pi +} +``` + +This extension makes `Circle` an instance of the `HasArea` trait. Specifically, it defines an implicit subclass of `HasArea` +which takes a `Circle` as argument and provides the given implementation. Hence, the implementation of the extension above would be like this + +```scala +implicit class CircleHasArea($this: Circle) extends HasArea { + def area = $this.radius * $this.radius * math.Pi +} +``` + +An instance definition can thus provide a kind of "implements" relationship that can be defined independently of the types it connects. + +### Generic Instance Declarations + +Just like extension methods, instance declarations can also be generic and their type parameters can have bounds. + +For example, assume we have the following two traits, which define binary and unary (infix) equality tests: + +```scala +trait Eql[T] { + def eql (x: T, y: T): Boolean +} + +trait HasEql[T] { + def === (that: T): Boolean +} +``` + +The following extension makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: + +```scala +extension HasEqlImpl[T : Eql] for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +### Syntax of Extensions + +The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + + TmplDef ::= ... + | ‘extension’ ExtensionDef + ExtensionDef ::= id [ExtensionParams] 'for' Type ExtensionClause + ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md new file mode 100644 index 000000000000..0a248018cc3e --- /dev/null +++ b/docs/docs/reference/extend/translation.md @@ -0,0 +1,85 @@ +--- +layout: doc-page +title: "Translation of Extensions" +--- + +Extensons are closely related to implicit classes and can be translated into them. In short, +an extension that just adds extension methods translates into an implicit value class whereas an instance declaration translates into a regular implicit class. The following sections sketch this translation. + +Conversely, it is conceivable (and desirable) to replace most usages of implicit classes and value classes by extensions and [opaque types](../opaques.html). We plan to [drop](../dropped/implicit-value-classes.html) +these constructs in future versions of the language. Once that is achieved, the translations described +below can be simply composed with the existing translations of implicit and value classes into the core language. It is +not necessary to retain implicit and value classes as an intermediate step. + + +### Translation of Extension Methods + +Assume an extension + + extension for { } + +where both `` and `` can be absent. +For simplicity assume that there are no context bounds on any of the type parameters +in ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. + +The extension is translated to the following implicit value class: + + implicit class (private val $this: ) extends AnyVal { + import $this._ + + } + +Here `` results from `` by augmenting any definition in with the parameters and replacing any occurrence of `this` with `$this`. + +For example, the extension + +```scala +extension SeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class SeqOps[T](private val $this: List[T]) extends AnyVal { + import $this._ + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Instance Declarations + +Now, assume an extension + + extension if for : { } + +where ``, `` and `` are as before. +This extension is translated to + + implicit class ($this: ) extends { + import $this._ + + } + +As before, `` is computed from `` by replacing any occurrence of `this` with `$this`. However, all parameters in now stay on the class definition, instead of being distributed to all members in ``. This is necessary in general, since `` might contain value definitions or other statements that cannot be +parameterized. + +For example, the extension + +```scala +extension HasEqlImpl[T : Eql) for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +would be translated to + +```scala +implicit class HasEqlForEql[T]($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + import $this._ + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) +} +``` From 3effb9631d25f7971083cce0205cee19c25f73ac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 16:46:36 +0100 Subject: [PATCH 29/33] Parser for fixed syntax --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 5 ++ .../dotty/tools/dotc/parsing/Parsers.scala | 83 +++++++++++++++---- .../src/dotty/tools/dotc/parsing/Tokens.scala | 5 +- docs/docs/internals/syntax.md | 4 +- 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 3f2136b8ce17..7e5982db3b7d 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,6 +40,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } + /** extend extended impl */ + case class Extension(name: TypeName, extended: Tree, impl: Template) extends DefTree + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -139,6 +142,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Enum() extends Mod(Flags.EmptyFlags) case class EnumCase() extends Mod(Flags.EmptyFlags) + + case class InstanceDecl() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ce7e56b1cd31..b6e3f4fd4aa9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1058,7 +1058,7 @@ object Parsers { * | PostfixExpr `match' `{' CaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] - * Ascription ::= `:' CompoundType + * Ascription ::= `:' InfixType * | `:' Annotation {Annotation} * | `:' `_' `*' */ @@ -1178,6 +1178,13 @@ object Parsers { t } + /** Ascription ::= `:' InfixType + * | `:' Annotation {Annotation} + * | `:' `_' `*' + * PatternAscription ::= `:' TypePattern + * | `:' `_' `*' + * TypePattern ::= RefinedType + */ def ascription(t: Tree, location: Location.Value) = atPos(startOffset(t)) { in.skipToken() in.token match { @@ -1539,7 +1546,7 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() } else Nil - /** Pattern1 ::= PatVar Ascription + /** Pattern1 ::= PatVar PatternAscription * | Pattern2 */ def pattern1(): Tree = { @@ -1831,8 +1838,10 @@ object Parsers { * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] + * ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’) + * ImplicitMods ::= `implicit` [`unused`] | `unused` `implicit` */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { + def paramClauses(owner: Name, ofCaseClass: Boolean = false, ofExtension: Boolean = false): List[List[ValDef]] = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once var firstClauseOfCaseClass = ofCaseClass @@ -1878,7 +1887,7 @@ object Parsers { } } def paramClause(): List[ValDef] = inParens { - if (in.token == RPAREN) Nil + if (!ofExtension && in.token == RPAREN) Nil else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -1891,7 +1900,8 @@ object Parsers { } } funArgMods() - + if (ofExtension && !imods.is(Implicit)) + syntaxError(i"parameters of extension must be implicit") commaSeparated(() => param()) } } @@ -1901,7 +1911,7 @@ object Parsers { imods = EmptyModifiers paramClause() :: { firstClauseOfCaseClass = false - if (imods is Implicit) Nil else clauses() + if (imods.is(Implicit) || ofExtension) Nil else clauses() } } else Nil } @@ -2159,6 +2169,7 @@ object Parsers { /** TmplDef ::= ([`case'] ‘class’ | trait’) ClassDef * | [`case'] `object' ObjectDef * | `enum' EnumDef + * | `extension' ExtensionDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { in.token match { @@ -2174,13 +2185,15 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => enumDef(start, mods, atPos(in.skipToken()) { Mod.Enum() }) + case EXTENSION => + extensionDef(start, posMods(start, mods)) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) EmptyTree } } - /** ClassDef ::= id ClassConstr TemplateOpt + /** ClassDef ::= id ClassConstr [TemplateClause] */ def classDef(start: Offset, mods: Modifiers): TypeDef = atPos(start, nameStart) { classDefRest(start, mods, ident().toTypeName) @@ -2188,7 +2201,7 @@ object Parsers { def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = { val constr = classConstr(name, isCaseClass = mods is Case) - val templ = templateOpt(constr) + val templ = templateClauseOpt(constr) TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start)) } @@ -2197,7 +2210,7 @@ object Parsers { def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val vparamss = paramClauses(owner, ofCaseClass = isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } @@ -2206,14 +2219,14 @@ object Parsers { def constrModsOpt(owner: Name): Modifiers = modifiers(accessModifierTokens, annotsAsMods()) - /** ObjectDef ::= id TemplateOpt + /** ObjectDef ::= id [TemplateClause] */ def objectDef(start: Offset, mods: Modifiers): ModuleDef = atPos(start, nameStart) { objectDefRest(start, mods, ident()) } def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = { - val template = templateOpt(emptyConstructor) + val template = templateClauseOpt(emptyConstructor) ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) } @@ -2223,7 +2236,7 @@ object Parsers { val modName = ident() val clsName = modName.toTypeName val constr = classConstr(clsName) - val impl = templateOpt(constr, isEnum = true) + val impl = templateClauseOpt(constr, isEnum = true, bodyRequired = true) TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) } @@ -2269,6 +2282,36 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } + /** ExtensionDef ::= id [ExtensionParams] 'for' AnnotType ExtensionClause + * ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + * ExtensionClause ::= [`:` Template] + * | [nl] `{` `def` DefDef {semi `def` DefDef} `}` + */ + def extensionDef(start: Offset, mods: Modifiers): Extension = atPos(start, nameStart) { + val name = ident().toTypeName + val tparams = typeParamClauseOpt(ParamOwner.Class) + val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) + val constr = makeConstructor(Nil, vparamss) + accept(FOR) + val extended = annotType() + val templ = + if (in.token == COLON) { + in.nextToken() + template(constr, bodyRequired = true)._1 + } + else { + val templ = templateClauseOpt(constr, bodyRequired = true) + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + templ + } + Extension(name, extended, templ) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2285,26 +2328,30 @@ object Parsers { * @return a pair consisting of the template, and a boolean which indicates * whether the template misses a body (i.e. no {...} part). */ - def template(constr: DefDef, isEnum: Boolean = false): (Template, Boolean) = { + def template(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): (Template, Boolean) = { newLineOptWhenFollowedBy(LBRACE) if (in.token == LBRACE) (templateBodyOpt(constr, Nil, isEnum), false) else { val parents = tokenSeparated(WITH, constrApp) newLineOptWhenFollowedBy(LBRACE) - if (isEnum && in.token != LBRACE) + if (bodyRequired && in.token != LBRACE) syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) val missingBody = in.token != LBRACE (templateBodyOpt(constr, parents, isEnum), missingBody) } } - /** TemplateOpt = [`extends' Template | TemplateBody] + /** TemplateClause = `extends' Template | TemplateBody + * TemplateClauseOpt = [TemplateClause] */ - def templateOpt(constr: DefDef, isEnum: Boolean = false): Template = - if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum)._1 } + def templateClauseOpt(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): Template = + if (in.token == EXTENDS) { + in.nextToken() + template(constr, isEnum, bodyRequired)._1 + } else { newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) template(constr, isEnum)._1 + if (in.token == LBRACE || bodyRequired) template(constr, isEnum, bodyRequired)._1 else Template(constr, Nil, EmptyValDef, Nil) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 5becc9161309..e9e692d70c12 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -179,6 +179,7 @@ object Tokens extends TokensCommon { final val ENUM = 63; enter(ENUM, "enum") final val ERASED = 64; enter(ERASED, "erased") final val OPAQUE = 65; enter(OPAQUE, "opaque") + final val EXTENSION = 66; enter(EXTENSION, "extension") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -199,7 +200,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, OPAQUE) + final val alphaKeywords = tokenRange(IF, EXTENSION) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -220,7 +221,7 @@ object Tokens extends TokensCommon { final val canStartBindingTokens = identifierTokens | BitSet(USCORE, LPAREN) - final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) + final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, EXTENSION, CASECLASS, CASEOBJECT) final val dclIntroTokens = BitSet(DEF, VAL, VAR, TYPE) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 2a16fc06e323..c7b8e622a726 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -330,13 +330,13 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef + | ‘extension’ ExtensionDef ClassDef ::= id ClassConstr [TemplateClause] ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [TemplateClause] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody TypeDef(mods, name, template) -Extension ::= 'extension' id [ExtensionParams] Extension(name, templ) - 'for' Type ExtensionClause +ExtensionDef ::= id [ExtensionParams] 'for' AnnotType ExtensionClause Extension(name, type, templ) ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] ExtensionClause ::= [`:` Template] | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ From a8d48e91de51bb6ad5309e0987b5528563be249f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 18:04:04 +0100 Subject: [PATCH 30/33] Desugarings for extensions --- .../src/dotty/tools/dotc/ast/Desugar.scala | 106 +++++++++++++++--- compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 +- .../dotty/tools/dotc/config/Printers.scala | 1 + 3 files changed, 94 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a9999c58f81e..8ed5398ef5f4 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -11,6 +11,7 @@ import language.higherKinds import typer.FrontEnd import collection.mutable.ListBuffer import util.Property +import config.Printers.desugr import reporting.diagnostic.messages._ import reporting.trace @@ -154,6 +155,25 @@ object desugar { ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit) } + private def desugarTypeBindings( + bindings: List[TypeDef], + forPrimaryConstructor: Boolean = false)(implicit ctx: Context): (List[TypeDef], List[ValDef]) = { + val epbuf = new ListBuffer[ValDef] + def desugarContextBounds(rhs: Tree): Tree = rhs match { + case ContextBounds(tbounds, cxbounds) => + epbuf ++= makeImplicitParameters(cxbounds, forPrimaryConstructor) + tbounds + case LambdaTypeTree(tparams, body) => + cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) + case _ => + rhs + } + val bindings1 = bindings mapConserve { tparam => + cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) + } + (bindings1, epbuf.toList) + } + /** Expand context bounds to evidence params. E.g., * * def f[T >: L <: H : B](params) @@ -171,21 +191,8 @@ object desugar { private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = { val DefDef(name, tparams, vparamss, tpt, rhs) = meth val mods = meth.mods - val epbuf = new ListBuffer[ValDef] - def desugarContextBounds(rhs: Tree): Tree = rhs match { - case ContextBounds(tbounds, cxbounds) => - epbuf ++= makeImplicitParameters(cxbounds, isPrimaryConstructor) - tbounds - case LambdaTypeTree(tparams, body) => - cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) - case _ => - rhs - } - val tparams1 = tparams mapConserve { tparam => - cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) - } - - val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList) + val (tparams1, evidenceParams) = desugarTypeBindings(tparams, isPrimaryConstructor) + val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), evidenceParams) /** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */ def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { @@ -293,6 +300,7 @@ object desugar { def isAnyVal(tree: Tree): Boolean = tree match { case Ident(tpnme.AnyVal) => true case Select(qual, tpnme.AnyVal) => isScala(qual) + case TypedSplice(tree) => tree.tpe.isRef(defn.AnyValClass) case _ => false } def isScala(tree: Tree): Boolean = tree match { @@ -749,6 +757,73 @@ object desugar { Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) } + /** extension id for : { } + * -> + * implicit class ($this: ) + * extends { + * import $this._ + * + * } + * + * where + * + * (, ) = desugarTypeBindings() + * = concatenated with in one clause + * = with each occurrence of unqualified `this` substituted by `$this`. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * extension for { } + * -> + * implicit class (private val $this: ) + * extends AnyVal { + * import $this._ + * + * } + * + * where + * + * = where each method definition gets as last parameter section. + */ + def extension(tree: Extension)(implicit ctx: Context): Tree = { + val Extension(name, extended, impl) = tree + val isSimpleExtension = impl.parents.isEmpty + + val firstParams = ValDef(nme.SELF, extended, EmptyTree).withFlags(Private | Local | ParamAccessor) :: Nil + val body1 = substThis.transform(impl.body) + val impl1 = + if (isSimpleExtension) { + val (typeParams, evidenceParams) = + desugarTypeBindings(impl.constr.tparams, forPrimaryConstructor = false) + cpy.Template(impl)( + constr = cpy.DefDef(impl.constr)(tparams = typeParams, vparamss = firstParams :: Nil), + parents = ref(defn.AnyValType) :: Nil, + body = body1.map { + case ddef: DefDef => + def resetFlags(vdef: ValDef) = + vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) + val originalParams = impl.constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) + case other => + other + }) + } + else + cpy.Template(impl)( + constr = cpy.DefDef(impl.constr)(vparamss = firstParams :: impl.constr.vparamss), + body = body1) + val icls = TypeDef(name, impl1).withMods(tree.mods.withAddedMod(Mod.Extension()) | Implicit) + desugr.println(i"desugar $extended --> $icls") + classDef(icls) + } + + private val substThis = new UntypedTreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case This(Ident(tpnme.EMPTY)) => Ident(nme.SELF).withPos(tree.pos) + case _ => super.transform(tree) + } + } + def defTree(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: ValDef => valDef(tree) case tree: TypeDef => if (tree.isClassDef) classDef(tree) else tree @@ -757,6 +832,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) + case tree: Extension => extension(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 7e5982db3b7d..e0585205e55a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -41,7 +41,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } /** extend extended impl */ - case class Extension(name: TypeName, extended: Tree, impl: Template) extends DefTree + case class Extension(name: TypeName, extended: Tree, impl: Template) extends MemberDef case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -143,7 +143,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class EnumCase() extends Mod(Flags.EmptyFlags) - case class InstanceDecl() extends Mod(Flags.EmptyFlags) + case class Extension() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index bd8cb9844c0c..d61764c0a7eb 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,6 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter + val desugr: Printer = new Printer val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter From 20843714471da24735334078ecd0748b02a92add Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Mar 2018 21:07:22 +0100 Subject: [PATCH 31/33] Avoid `extension` as an identifier. It's now a keyword, need to enclose in backticks when used as an identifier. --- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 4 ++-- compiler/src/dotty/tools/dotc/config/Settings.scala | 2 +- compiler/src/dotty/tools/dotc/fromtasty/Debug.scala | 2 +- compiler/src/dotty/tools/io/AbstractFile.scala | 6 +++--- compiler/src/dotty/tools/io/JarArchive.scala | 4 ++-- compiler/src/dotty/tools/io/Path.scala | 12 ++++++------ .../dotc/transform/PatmatExhaustivityTest.scala | 4 ++-- tests/neg/i2494.scala | 2 +- tests/pos/t8306.scala | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index ca21867fd711..5d8fe2a37d70 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -57,7 +57,7 @@ class GenBCode extends Phase { if (myOutput eq null) { val path = Directory(ctx.settings.outputDir.value) myOutput = - if (path.extension == "jar") JarArchive.create(path) + if (path.`extension` == "jar") JarArchive.create(path) else new PlainDirectory(path) } myOutput diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 8ed5398ef5f4..047e23d2e291 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -785,7 +785,7 @@ object desugar { * * = where each method definition gets as last parameter section. */ - def extension(tree: Extension)(implicit ctx: Context): Tree = { + def extensionDef(tree: Extension)(implicit ctx: Context): Tree = { val Extension(name, extended, impl) = tree val isSimpleExtension = impl.parents.isEmpty @@ -832,7 +832,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) - case tree: Extension => extension(tree) + case tree: Extension => extensionDef(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 6343341b29f4..750bec00989a 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -144,7 +144,7 @@ object Settings { Path(arg) match { case _: Directory => update(arg, args) - case p if p.extension == "jar" => + case p if p.`extension` == "jar" => update(arg, args) case _ => fail(s"'$arg' does not exist or is not a directory", args) diff --git a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala index f44ad7d63714..48abbf6de36b 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala @@ -36,7 +36,7 @@ object Debug { val fromTastyOut = Files.createDirectory(tmpOut.resolve("from-tasty")) val ext = "hasTasty" - val classes = Directory(fromSourcesOut).walk.filter(x => x.isFile && x.extension == ext).map { x => + val classes = Directory(fromSourcesOut).walk.filter(x => x.isFile && x.`extension` == ext).map { x => val source = x.toString // transform foo/bar/Baz.hasTasty into foo.bar.Baz source.substring(fromSourcesOut.toString.length + 1, source.length - ext.length - 1).replace('/', '.') diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index d0155ace3711..748144c6164a 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -93,8 +93,8 @@ abstract class AbstractFile extends Iterable[AbstractFile] { def canonicalPath: String = if (jpath == null) path else jpath.normalize.toString /** Checks extension case insensitively. */ - def hasExtension(other: String) = extension == other.toLowerCase - private val extension: String = Path.extension(name) + def hasExtension(other: String) = `extension` == other.toLowerCase + private val `extension`: String = Path.`extension`(name) /** The absolute file, if this is a relative file. */ def absolute: AbstractFile @@ -122,7 +122,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] { } /** Does this abstract file represent something which can contain classfiles? */ - def isClassContainer = isDirectory || (jpath != null && (extension == "jar" || extension == "zip")) + def isClassContainer = isDirectory || (jpath != null && (`extension` == "jar" || `extension` == "zip")) /** Create a file on disk, if one does not exist already. */ def create(): Unit diff --git a/compiler/src/dotty/tools/io/JarArchive.scala b/compiler/src/dotty/tools/io/JarArchive.scala index 0960160d52b7..f600cdd099a6 100644 --- a/compiler/src/dotty/tools/io/JarArchive.scala +++ b/compiler/src/dotty/tools/io/JarArchive.scala @@ -15,14 +15,14 @@ class JarArchive private (root: Directory) extends PlainDirectory(root) { object JarArchive { /** Create a new jar file. Overwrite if file already exists */ def create(path: Path): JarArchive = { - require(path.extension == "jar") + require(path.`extension` == "jar") path.delete() open(path, create = true) } /** Create a jar file. */ def open(path: Path, create: Boolean = false): JarArchive = { - require(path.extension == "jar") + require(path.`extension` == "jar") // creating a new zip file system by using the JAR URL syntax: // https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html diff --git a/compiler/src/dotty/tools/io/Path.scala b/compiler/src/dotty/tools/io/Path.scala index fbd2a4ac92c7..68bca86d54b4 100644 --- a/compiler/src/dotty/tools/io/Path.scala +++ b/compiler/src/dotty/tools/io/Path.scala @@ -35,10 +35,10 @@ import scala.util.Random.alphanumeric object Path { def isExtensionJarOrZip(jpath: JPath): Boolean = isExtensionJarOrZip(jpath.getFileName.toString) def isExtensionJarOrZip(name: String): Boolean = { - val ext = extension(name) + val ext = `extension`(name) ext == "jar" || ext == "zip" } - def extension(name: String): String = { + def `extension`(name: String): String = { var i = name.length - 1 while (i >= 0 && name.charAt(i) != '.') i -= 1 @@ -138,7 +138,7 @@ class Path private[io] (val jpath: JPath) { if (p isSame this) Nil else p :: p.parents } // if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg"), otherwise "" - def extension: String = { + def `extension`: String = { var i = name.length - 1 while (i >= 0 && name.charAt(i) != '.') i -= 1 @@ -148,17 +148,17 @@ class Path private[io] (val jpath: JPath) { } // compares against extensions in a CASE INSENSITIVE way. def hasExtension(ext: String, exts: String*) = { - val lower = extension.toLowerCase + val lower = `extension`.toLowerCase ext.toLowerCase == lower || exts.exists(_.toLowerCase == lower) } // returns the filename without the extension. - def stripExtension: String = name stripSuffix ("." + extension) + def stripExtension: String = name stripSuffix ("." + `extension`) // returns the Path with the extension. def addExtension(ext: String): Path = new Path(jpath.resolveSibling(name + ext)) // changes the existing extension out for a new one, or adds it // if the current path has none. def changeExtension(ext: String): Path = - if (extension == "") addExtension(ext) + if (`extension` == "") addExtension(ext) else new Path(jpath.resolveSibling(stripExtension + "." + ext)) // conditionally execute diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 53c6e0c53238..9da936d4d3d8 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -45,7 +45,7 @@ class PatmatExhaustivityTest { val reporter = TestReporter.simplifiedReporter(new PrintWriter(stringBuffer)) val files = Directory(path).list.toList - .filter(f => f.extension == "scala" || f.extension == "java" ) + .filter(f => f.`extension` == "scala" || f.`extension` == "java" ) .map(_.jpath.toString) try { @@ -69,7 +69,7 @@ class PatmatExhaustivityTest { @Test def patmatExhaustivity: Unit = { val res = Directory(testsDir).list.toList - .filter(f => f.extension == "scala" || f.isDirectory) + .filter(f => f.`extension` == "scala" || f.isDirectory) .map { f => if (f.isDirectory) compileDir(f.jpath) diff --git a/tests/neg/i2494.scala b/tests/neg/i2494.scala index 21c11242878d..afdd8e92fce5 100644 --- a/tests/neg/i2494.scala +++ b/tests/neg/i2494.scala @@ -1,2 +1,2 @@ -enum +enum // error object // error // error diff --git a/tests/pos/t8306.scala b/tests/pos/t8306.scala index e04b054eb9bb..f6b930bada74 100644 --- a/tests/pos/t8306.scala +++ b/tests/pos/t8306.scala @@ -1,6 +1,6 @@ class Si8306 { def foo: Int = 123 - lazy val extension: Int = + lazy val ext: Int = foo match { case idx if idx != -1 => 15 case _ => 17 From 68ab0bb5a632ae376bb1c0dba34003199d9f8521 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Mar 2018 11:03:07 +0100 Subject: [PATCH 32/33] Pretty printing, fixes, and tests --- .../src/dotty/tools/dotc/ast/Desugar.scala | 20 ++- compiler/src/dotty/tools/dotc/ast/untpd.scala | 21 ++- .../dotty/tools/dotc/config/Printers.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 8 +- .../tools/dotc/printing/RefinedPrinter.scala | 22 ++- .../src/dotty/tools/dotc/typer/Namer.scala | 3 +- .../transform/PatmatExhaustivityTest.scala | 2 +- .../reference/extend/extension-methods.md | 2 +- tests/neg/extensions.scala | 45 +++++ tests/pending/pos/blueSkyExtensions1.scala | 134 ++++++++++++++ tests/pos/opaque-extension.scala | 35 ++++ tests/run/extensions.check | 16 ++ tests/run/extensions.scala | 166 ++++++++++++++++++ 13 files changed, 454 insertions(+), 22 deletions(-) create mode 100644 tests/neg/extensions.scala create mode 100644 tests/pending/pos/blueSkyExtensions1.scala create mode 100644 tests/pos/opaque-extension.scala create mode 100644 tests/run/extensions.check create mode 100644 tests/run/extensions.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 047e23d2e291..5f579e963203 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -786,23 +786,24 @@ object desugar { * = where each method definition gets as last parameter section. */ def extensionDef(tree: Extension)(implicit ctx: Context): Tree = { - val Extension(name, extended, impl) = tree + val Extension(name, constr, extended, impl) = tree val isSimpleExtension = impl.parents.isEmpty val firstParams = ValDef(nme.SELF, extended, EmptyTree).withFlags(Private | Local | ParamAccessor) :: Nil - val body1 = substThis.transform(impl.body) + val importSelf = Import(Ident(nme.SELF), Ident(nme.WILDCARD) :: Nil) + val body1 = importSelf :: substThis.transform(impl.body) val impl1 = if (isSimpleExtension) { val (typeParams, evidenceParams) = - desugarTypeBindings(impl.constr.tparams, forPrimaryConstructor = false) + desugarTypeBindings(constr.tparams, forPrimaryConstructor = false) cpy.Template(impl)( - constr = cpy.DefDef(impl.constr)(tparams = typeParams, vparamss = firstParams :: Nil), + constr = cpy.DefDef(constr)(tparams = typeParams, vparamss = firstParams :: Nil), parents = ref(defn.AnyValType) :: Nil, body = body1.map { case ddef: DefDef => def resetFlags(vdef: ValDef) = vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) - val originalParams = impl.constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + val originalParams = constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) case other => other @@ -810,10 +811,13 @@ object desugar { } else cpy.Template(impl)( - constr = cpy.DefDef(impl.constr)(vparamss = firstParams :: impl.constr.vparamss), + constr = cpy.DefDef(constr)(vparamss = firstParams :: constr.vparamss), body = body1) - val icls = TypeDef(name, impl1).withMods(tree.mods.withAddedMod(Mod.Extension()) | Implicit) - desugr.println(i"desugar $extended --> $icls") + val mods1 = + if (isSimpleExtension) tree.mods + else tree.mods.withAddedMod(Mod.InstanceDcl()) + val icls = TypeDef(name, impl1).withMods(mods1 | Implicit) + desugr.println(i"desugar $tree --> $icls") classDef(icls) } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e0585205e55a..afd9cf35d89a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,8 +40,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } - /** extend extended impl */ - case class Extension(name: TypeName, extended: Tree, impl: Template) extends MemberDef + /** extension name tparams vparamss for tpt impl + * + * where `tparams` and `vparamss` are part of `constr`. + */ + case class Extension(name: TypeName, constr: DefDef, tpt: Tree, impl: Template) + extends MemberDef{ + type ThisTree[-T >: Untyped] <: Trees.NameTree[T] with Trees.MemberDef[T] with Extension + def withName(name: Name)(implicit ctx: Context) = cpy.Extension(this)(name.toTypeName, constr, tpt, impl) + } case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree @@ -143,7 +150,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class EnumCase() extends Mod(Flags.EmptyFlags) - case class Extension() extends Mod(Flags.EmptyFlags) + case class InstanceDcl() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions @@ -416,6 +423,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: ModuleDef if (name eq tree.name) && (impl eq tree.impl) => tree case _ => finalize(tree, untpd.ModuleDef(name, impl)) } + def Extension(tree: Tree)(name: TypeName, constr: DefDef, tpt: Tree, impl: Template) = tree match { + case tree: Extension if (name eq tree.name) && (constr eq tree.constr) && (tpt eq tree.tpt) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Extension(name, constr, tpt, impl)) + } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree @@ -499,6 +510,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) + case Extension(name, constr, tpt, impl) => + cpy.Extension(tree)(name, transformSub(constr), transform(tpt), transformSub(impl)) case ParsedTry(expr, handler, finalizer) => cpy.ParsedTry(tree)(transform(expr), transform(handler), transform(finalizer)) case SymbolLit(str) => @@ -548,6 +561,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = tree match { case ModuleDef(name, impl) => this(x, impl) + case Extension(name, constr, tpt, impl) => + this(this(this(x, constr), tpt), impl) case ParsedTry(expr, handler, finalizer) => this(this(this(x, expr), handler), finalizer) case SymbolLit(str) => diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index d61764c0a7eb..db540ebf85b5 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,7 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter - val desugr: Printer = new Printer + val desugr: Printer = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index b6e3f4fd4aa9..99fd6119a28a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2291,16 +2291,16 @@ object Parsers { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Class) val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) - val constr = makeConstructor(Nil, vparamss) + val constr = makeConstructor(tparams, vparamss) accept(FOR) val extended = annotType() val templ = if (in.token == COLON) { in.nextToken() - template(constr, bodyRequired = true)._1 + template(emptyConstructor, bodyRequired = true)._1 } else { - val templ = templateClauseOpt(constr, bodyRequired = true) + val templ = templateClauseOpt(emptyConstructor, bodyRequired = true) def checkDef(tree: Tree) = tree match { case _: DefDef | EmptyValDef => // ok case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) @@ -2309,7 +2309,7 @@ object Parsers { templ.body.foreach(checkDef) templ } - Extension(name, extended, templ) + Extension(name, constr, extended, templ) } /* -------- TEMPLATES ------------------------------------------- */ diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index ee9a1df5921c..194fb0ee03ed 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -448,6 +448,17 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { modText(tree.mods, keywordStr("object"), isType = false) ~~ nameIdText(tree) ~ toTextTemplate(impl) } + case tree @ Extension(name, constr, tpt, impl) => + withEnclosingDef(tree) { + modText(tree.mods, keywordStr("extension")) ~~ + nameIdText(tree) ~ + { withEnclosingDef(constr) { + addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1)) + } + } ~ + " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ + toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) + } case SymbolLit(str) => "'" + str case InterpolatedString(id, segments) => @@ -629,7 +640,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { - val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl + val constr @ DefDef(_, tparams, vparamss, _, _) = impl.constr val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) } val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil val prefix: Text = @@ -640,6 +651,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } } + prefix ~ toTextTemplateBody(impl, keywordText(" extends") `provided` !ofNew, primaryConstrs) + } + + protected def toTextTemplateBody(impl: Template, leading: Text, leadingStats: List[Tree[_]] = Nil): Text = { + val Template(_, parents, self, _) = impl val parentsText = Text(parents map constrText, keywordStr(" with ")) val selfText = { val selfName = if (self.name == nme.WILDCARD) keywordStr("this") else self.name.toString @@ -657,9 +673,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { params ::: rest } else impl.body - val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}" + val bodyText = "{" ~~ selfText ~~ toTextGlobal(leadingStats ::: body, "\n") ~ "}" - prefix ~ (keywordText(" extends") provided !ofNew) ~~ parentsText ~~ bodyText + leading ~~ parentsText ~~ bodyText } protected def templateText(tree: TypeDef, impl: Template): Text = { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 089b359ada03..c68a084cfeb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -943,7 +943,8 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.pos, - traitReq = parent ne parents.head, stablePrefixReq = true) + traitReq = (parent `ne` parents.head) || original.mods.hasMod[Mod.InstanceDcl], + stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { case Select(qual: Super, _) if ctx.scala2Mode => diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 9da936d4d3d8..a961860f4c08 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -78,7 +78,7 @@ class PatmatExhaustivityTest { } val failed = res.filter { case (_, expected, actual) => expected != actual } - val ignored = Directory(testsDir).list.toList.filter(_.extension == "ignore") + val ignored = Directory(testsDir).list.toList.filter(_.`extension` == "ignore") failed.foreach { case (file, expected, actual) => println(s"\n----------------- incorrect output for $file --------------\n" + diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md index 1790f89b6e45..c4947391e513 100644 --- a/docs/docs/reference/extend/extension-methods.md +++ b/docs/docs/reference/extend/extension-methods.md @@ -122,7 +122,7 @@ object PostConditions { def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) - extenson Ensuring[T] for T { + extension Ensuring[T] for T { def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { implicit val wrapped = WrappedResult.wrap(this) assert(condition) diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala new file mode 100644 index 000000000000..bfb720a6ac28 --- /dev/null +++ b/tests/neg/extensions.scala @@ -0,0 +1,45 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extension CircleOps for Circle { + def circumference = this.radius * math.Pi * 2 + private val p = math.Pi // error: `def` expected + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + abstract class HasAreaClass extends HasArea + + extension Ops2 : HasArea {} // error: `def` expected + extension Ops for Circle extends HasArea {} // error: `def` expected + + extension Circle2 : HasArea { // error: `for` expected + def area = this.radius * this.radius * math.Pi + } + + extension Ops3 for Circle : HasAreaClass { // error: class HasAreaClass is not a trait + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + extension ListOps[T] for List[T] { + type I = Int // error: `def` expected + def second = this.tail.head + } + +// Specific trait implementations + + extension ListOps2 for List[Int] { self => // error: `def` expected + import java.lang._ // error: `def` expected + def maxx = (0 /: this)(_ `max` _) + } +} diff --git a/tests/pending/pos/blueSkyExtensions1.scala b/tests/pending/pos/blueSkyExtensions1.scala new file mode 100644 index 000000000000..56ec9f6e5ac7 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions1.scala @@ -0,0 +1,134 @@ +/** A blue sky sketch how one might evolve extensions to do type classes + * without the current gap between functional and OO patterns. + * Largely inspired by the way Rust does it. + * + * The main additions (to be worked out in detail) is a self type `This` and + * a mechanism that a trait can abstract over companions of classes that implement it. + * Companion types and methods are declared using `static` for now, just to + * pick some familiar notation. + * + * Ideas about `This` (still vague at the moment) + * + * - Treat it as an additional abstract type in a trait, prefixed by the name of the trait + * - An implementing (non-trait) class binds the `This` types of all the traits it implements + * to itself. + * - Later subclasses do not re-bind `This` types already bound by their superclasses. + * (so in that sense, `This`-binding is like trait parameterization, the first implementing + * classes determines the self type and the parameters of a trait) + * - Paramerized classes have parameterized `This` types (e.g. Functor below). + */ +import Predef.{any2stringadd => _, _} +object blueSkyExtensions { + +// Semigroup and Monoid + + trait SemiGroup { + def + (that: This): This + } + + trait Monoid extends SemiGroup { + static def unit: This + } + + extension IntMonoid for Int : Monoid { + static def unit = 0 + def + (that: Int) = this + that + } + + extension StringMonoid for String : Monoid { + static def unit = "" + def + (that: Int) = this ++ that + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + static def pure[A]: This[A] + def map[B](f: A => B): This[B] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.statics[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extension ListMonad[T] for List[T] : Monad[T] { + static def pure[A] = Nil + + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + + extension MonadFlatten[T[X]: Monad[X]] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + static def empty: This[A] + static def apply(xs: A*): This[A] + + def filter(p: A => Boolean): This[A] + } + + trait Iterable[A] extends MonoIterable[A] { + def map[B](f: A => B): This[B] + def flatMap[B](f: A => This[B]): This[B] + } + + extension StringMonoIterable for String : MonoIterable[Char] { + static type This[A] = String + static def empty = "" + static def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + + extension StringIterable for String : Iterable[Char] { + static type This[A] = IndexedSeq[A] + + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extension ListIterable[T] for List[T] : Iterable[T] { + static type This[A] = List[A] + static def empty = Nil + static def apply(xs: A*) = (xs /: Nil)(_ :: _) + + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } +} diff --git a/tests/pos/opaque-extension.scala b/tests/pos/opaque-extension.scala new file mode 100644 index 000000000000..69230356b0dc --- /dev/null +++ b/tests/pos/opaque-extension.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + extension LogarithmOps for Logarithm { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(this) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(this) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(this + that) + } + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l * l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = l3.toDouble + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/run/extensions.check b/tests/run/extensions.check new file mode 100644 index 000000000000..748a6ca11e55 --- /dev/null +++ b/tests/run/extensions.check @@ -0,0 +1,16 @@ +12.566370614359172 +(1,a) +true +false +true +false +true +false +true +false +2 +List(1, 2, 3) +3 +3 +false +true diff --git a/tests/run/extensions.scala b/tests/run/extensions.scala new file mode 100644 index 000000000000..1ed7ff89bda5 --- /dev/null +++ b/tests/run/extensions.scala @@ -0,0 +1,166 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extension CircleOps for Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + extension CircleHasArea for Circle : HasArea { + def area = radius * this.radius * math.Pi + } + +// Generic extendations + + extension ListOps[T] for List[T] { + def second = tail.head + } + +// Specific extendations + + extension IntListOps for List[Int] { + def maxx = (0 /: this)(_ `max` _) + } + + extension IntArrayOps for Array[Int] { + def maxx = (0 /: this)(_ `max` _) + } + +// Conditional extension methods + + case class Rectangle[T](x: T, y: T, width: T, height: T) + + trait Eql[T] { + def eql (x: T, y: T): Boolean + } + + extension RectangleOps[T: Eql] for Rectangle[T] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + + extension RectangleOps2[T](implicit ev: Eql[T]) for Rectangle[T] { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(width, height) + } + +// Simple generic extensions + + extension Pairer[T] for T { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic extensions + + trait HasEql[T] { + def === (that: T): Boolean + } + + extension HasEqlDeco[T : Eql] for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + extension InfixEql[T](implicit ev: Eql[T]) for T { + def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + extension RectangleHasEql[T : Eql] for Rectangle[T] : HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } +} + +object extensions2 { + import extensions.{Eql, HasEqlDeco} + // Nested generic arguments + + extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = (this :\ (Nil: List[T]))(_ ++ _) + } + + extension EqlDeco[T : Eql] for (T, T) { + def isSame = this._1 === this._2 + def isSame2 = HasEqlDeco(this._1) === this._2 + } + +} + +object docs { + + extension StringOps for Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = map(_.length).max + filter(_.length == maxLength) + } + } + + extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = foldLeft[List[T]](Nil)(_ ++ _) + } + + extension OrdSeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 + } + + object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + extension Ensuring[T] for T { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } + } + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} + +import extensions._ +import extensions2._ +object Test extends App { + val c = Circle(0, 1, 2) + println(c.area) + + implicit object IntHasEql extends Eql[Int] { + def eql (x: Int, y: Int): Boolean = x == y + } + + println(1 ~ "a") + + val r1 = Rectangle(0, 0, 2, 2) + val r2 = Rectangle(0, 0, 2, 3) + println(r1.isSquare) + println(r2.isSquare) + println(r2.isNotSquare) + println(r1.isNotSquare) + println(r1 === r1) + println(r1 === r2) + println(1 ==== 1) + println(1 ==== 2) + println(List(1, 2, 3).second) + println(List(List(1), List(2, 3)).flattened) + println(List(List(1), List(2, 3)).flattened.maxx) + println(Array(1, 2, 3).maxx) + println((2, 3).isSame) + println(EqlDeco((3, 3)).isSame) +} From e318da75ba8b3adb501bd7d564ff95b2ebd59b2a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 31 Mar 2018 18:15:21 +0200 Subject: [PATCH 33/33] Adapt RefinedPrinter to new scheme for modText --- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 194fb0ee03ed..83532f865477 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -450,7 +450,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case tree @ Extension(name, constr, tpt, impl) => withEnclosingDef(tree) { - modText(tree.mods, keywordStr("extension")) ~~ + modText(tree.mods, keywordStr("extension"), isType = false) ~~ nameIdText(tree) ~ { withEnclosingDef(constr) { addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1))