From f9cf3d0d03f5b935c3ae5cfcdfe6656c86d1c2dd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 22:48:16 +0200 Subject: [PATCH 01/14] Establish companionship directly Don't use synthetic companion methods to achieve this. The advantages of the direct approach are: - it's overall simpler - it can be more easily extended to opaque types --- .../src/dotty/tools/dotc/core/StdNames.scala | 3 +- .../tools/dotc/core/SymDenotations.scala | 70 ++++++++++--------- .../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/TreeUnpickler.scala | 9 +-- .../core/unpickleScala2/Scala2Unpickler.scala | 12 ++-- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 3 +- .../tools/dotc/transform/RestoreScopes.scala | 12 ---- .../dotty/tools/dotc/transform/SymUtils.scala | 9 --- .../tools/dotc/transform/TreeChecker.scala | 1 - .../src/dotty/tools/dotc/typer/Namer.scala | 6 +- 12 files changed, 50 insertions(+), 95 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 98e5ed22a024..b6e3604516e0 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -140,8 +140,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 BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" @@ -245,6 +243,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val DEFAULT_CASE: N = "defaultCase$" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 70fa2593c486..aa81140537af 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -471,15 +471,6 @@ object SymDenotations { final def isAnonymousModuleVal(implicit ctx: Context): Boolean = this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) - /** Is this a companion class 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): Boolean = - name.toTermName == nme.COMPANION_CLASS_METHOD || - name.toTermName == nme.COMPANION_MODULE_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. @@ -526,6 +517,11 @@ object SymDenotations { /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(implicit ctx: Context): Boolean = 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 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 @@ -597,12 +593,9 @@ object SymDenotations { /** Is this a "real" method? A real method is a method which is: * - not an accessor * - not an anonymous function - * - not a companion method */ final def isRealMethod(implicit ctx: Context): Boolean = - this.is(Method, butNot = Accessor) && - !isAnonymousFunction && - !isCompanionMethod + this.is(Method, butNot = Accessor) && !isAnonymousFunction /** Is this a getter? */ final def isGetter(implicit ctx: Context): Boolean = @@ -948,34 +941,32 @@ object SymDenotations { final def enclosingPackageClass(implicit ctx: Context): Symbol = if (this is PackageClass) symbol else owner.enclosingPackageClass + /** 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. * 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 registeredCompanion.sourceModule + + private def companionType(implicit ctx: Context): Symbol = + if (is(Package)) NoSymbol + else if (is(ModuleVal)) moduleClass.denot.companionType + 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. - * NoSymbol if this class does not exist. - */ + * 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 = - 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 - } + companionType.suchThat(_.isClass).symbol final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) @@ -1238,6 +1229,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 } @@ -1831,6 +1823,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/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 2d339cc013ba..dda067d926a9 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -186,15 +186,6 @@ trait Symbols { this: Context => val companionMethodFlags: FlagSet = Flags.Synthetic | Flags.Private | Flags.Method - def synthesizeCompanionMethod(name: Name, target: SymDenotation, owner: SymDenotation)(implicit ctx: Context): Symbol = - 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 65b57d7c99fa..f9e2d111ebc0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -161,9 +161,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 @@ -171,7 +168,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 3f9d75334c74..9af3319c85e5 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -168,10 +168,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/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 03851740c810..5c5bc7c76943 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -796,12 +796,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 (sym is Flags.ModuleClass) sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) - else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) - } + def isCodefined = roots.contains(companion.denot) == seenRoots.contains(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 40a968705998..ffe4b6457756 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -119,18 +119,14 @@ 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) + 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/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index ee43e7a37932..39b0d22c410c 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -262,8 +262,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 2abd04eb253e..002aa969327b 100644 --- a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala +++ b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala @@ -41,18 +41,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 05da7650d68e..9760f1e98cd4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -121,15 +121,6 @@ class SymUtils(val self: Symbol) extends AnyVal { self } - def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context): Any = { - 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 6ad359bc7d61..6b1b7079b493 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -370,7 +370,6 @@ class TreeChecker extends Phase with SymTransformer { def isNonMagicalMethod(x: Symbol) = x.is(Method) && - !x.isCompanionMethod && !x.isValueClassConvertMethod && !(x.is(Macro) && ctx.phase.refChecked) && !x.name.is(DocArtifactName) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index fe1c85a915cf..22fbb882a441 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -609,10 +609,8 @@ 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 - } + modl.registerCompanion(claz) + claz.registerCompanion(modl) } def createCompanionLinks(implicit ctx: Context): Unit = { From 60a14d4624e270dd25bba93b9a56d2d2ff7e3521 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 11 Oct 2018 16:27:51 +0200 Subject: [PATCH 02/14] Avoid follow-on error when trying to resolve overloads resolve overload errors should be suppressed if some types are already erroenous. --- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 3 ++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 28 +++++++++---------- .../src/dotty/tools/dotc/typer/Typer.scala | 7 +++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d77fb10cef0b..62b33dfbdc99 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -280,7 +280,7 @@ object Types { final def isError(implicit ctx: Context): Boolean = stripTypeVar.isInstanceOf[ErrorType] /** 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 { diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 8a3fe936c459..91960ed68850 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -335,6 +335,9 @@ object ProtoTypes { def isDropped: Boolean = state.toDrop + override def isErroneous(implicit ctx: Context): Boolean = + state.typedArgs.tpes.exists(_.widen.isErroneous) + override def toString: String = 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 6f56eafd7636..38484974b650 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -232,21 +232,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 { - 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) - } + 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) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 7f3c5e11157a..36bab5e808c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2244,8 +2244,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)) + } } } From 6b7a1a71fe33647ad116ad64b997520e445b1a88 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 11 Oct 2018 16:30:41 +0200 Subject: [PATCH 03/14] Print types and term flags separately Distinguish what is printed by previous knowledge whether the symbol is a term or a type. --- .../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 45aeeef52ec1..c7c30e871941 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -435,15 +435,20 @@ object Flags { // --------- Combined Flag Sets and Conjunctions ---------------------- /** Flags representing source modifiers */ - final val SourceModifierFlags: FlagSet = - commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) + private val CommonSourceModifierFlags: FlagSet = + commonFlags(Private, Protected, Final, Case, Implicit, Override, JavaStatic) + + final val TypeSourceModifierFlags: FlagSet = + CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed + + final val TermSourceModifierFlags: FlagSet = + CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased /** Flags representing modifiers that can appear in trees */ final val ModifierFlags: FlagSet = - SourceModifierFlags | Module | Param | Synthetic | Package | Local | - commonFlags(Mutable) - // | 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 7d1ebde30bdf..de0da060a01f 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -14,7 +14,7 @@ import dotty.tools.dotc.core.StdNames._ 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 blockToText[T >: Untyped](block: Block[T]): Text = block match { @@ -48,8 +48,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, tree.symbol, keywordStr(if ((tree).mods is Trait) "trait" else "class")) - else modText(tree.mods, tree.symbol, keywordStr("object"), suppress = Final | Module) + if (!tree.mods.is(Module)) modText(tree.mods, tree.symbol, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) + else modText(tree.mods, tree.symbol, keywordStr("object"), isType = false) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ "" } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6b81f57dceed..fdbf6963f18b 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -68,7 +68,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override protected def recursionLimitExceeded(): Unit = {} - protected val PrintableFlags: FlagSet = (SourceModifierFlags | Module | Local).toCommonFlags + protected def PrintableFlags(isType: Boolean): FlagSet = { + if (isType) TypeSourceModifierFlags | Module | Local + else TermSourceModifierFlags | Module | Local + }.toCommonFlags override def nameString(name: Name): String = if (ctx.settings.YdebugNames.value) name.debugString else name.toString @@ -440,7 +443,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree @ TypeDef(name, rhs) => def typeDefText(tparamsText: => Text, rhsText: => Text) = dclTextOr(tree) { - modText(tree.mods, tree.symbol, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ + modText(tree.mods, tree.symbol, keywordStr("type"), isType = true) ~~ + (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { @@ -479,7 +483,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { - modText(tree.mods, NoSymbol, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) + modText(tree.mods, NoSymbol, keywordStr("object"), isType = false) ~~ + nameIdText(tree) ~ toTextTemplate(impl) } case SymbolLit(str) => "'" + str @@ -534,8 +539,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { t ~ cxBoundToText(cxb) } case PatDef(mods, pats, tpt, rhs) => - modText(mods, NoSymbol, keywordStr("val")) ~~ toText(pats, ", ") ~ optAscription(tpt) ~ - optText(rhs)(" = " ~ _) + modText(mods, NoSymbol, 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 ") ~ _) @@ -658,7 +663,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, tree.symbol, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ + modText(tree.mods, tree.symbol, keywordStr(if (tree.mods is Mutable) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } } @@ -667,7 +672,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, tree.symbol, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + val prefix = modText(tree.mods, tree.symbol, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ optText(tree.rhs)(" = " ~ _) @@ -682,7 +687,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val prefix: Text = if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt else { - var modsText = modText(constr.mods, constr.symbol, "") + var modsText = modText(constr.mods, constr.symbol, "", isType = false) if (!modsText.isEmpty) modsText = " " ~ modsText if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } @@ -709,7 +714,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def templateText(tree: TypeDef, impl: Template): Text = { - val decl = modText(tree.mods, tree.symbol, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + val decl = modText(tree.mods, tree.symbol, 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 "") } @@ -732,12 +737,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - protected def modText(mods: untpd.Modifiers, sym: Symbol, kw: String, suppress: FlagSet = EmptyFlags): Text = { // DD + protected def modText(mods: untpd.Modifiers, sym: Symbol, 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 &~ suppress - else PrintableFlags &~ suppress + else if (suppressKw) PrintableFlags(isType) &~ Private + else PrintableFlags(isType) if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes val flags = (if (sym.exists) sym.flags else (mods.flags)) & flagMask val flagsText = if (flags.isEmpty) "" else keywordStr(flags.toString) @@ -811,7 +816,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 0b02cf75555f..9ca177f6f5f2 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala @@ -20,7 +20,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 756cd0a0ab8ba2d5f52be36c98206420732e2503 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 22:52:25 +0200 Subject: [PATCH 04/14] Handle higher-kinded GADT bounds It's a remainder from the effort to implement opaque types as GADTs. It has nothing to do with the new opaque type scheme but it fixes a missing piece: GADT constraints can affect higher-kinded type parameters, so these should be taken into account. --- .../dotty/tools/dotc/core/TypeComparer.scala | 17 +++++++++++------ compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e3fa098586ac..60ad1963ee29 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -830,9 +830,9 @@ class TypeComparer(initctx: Context) extends 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) && !tycon2bounds.isInstanceOf[MatchAlias]) - if (tyconIsTypeRef) recur(tp1, tp2.superType) + if (followSuperType) recur(tp1, tp2.superType) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -841,13 +841,15 @@ class TypeComparer(initctx: Context) extends 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) || defn.isTypelevel_S(tycon2.symbol) && compareS(tp2, tp1, fromBelow = true) || { 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 => tycon2.name.toString.startsWith("Tuple") && defn.isTupleType(tp2) && isSubType(tp1, tp2.toNestedPairs) || @@ -883,8 +885,11 @@ class TypeComparer(initctx: Context) extends ConstraintHandling { case tycon1: TypeRef => val sym = tycon1.symbol !sym.isClass && ( - defn.isTypelevel_S(sym) && compareS(tp1, tp2, fromBelow = false) || - recur(tp1.superType, tp2)) + defn.isTypelevel_S(sym) && compareS(tp1, tp2, fromBelow = false) || { + 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/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 62b33dfbdc99..09f8a3e36ee9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -563,7 +563,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) From e0efae1ed595b5c7f9307b05cda0c0fc17eae6fa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 22:57:50 +0200 Subject: [PATCH 05/14] Parsing and pickling of opaque types --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 + .../src/dotty/tools/dotc/core/Flags.scala | 13 ++++-- .../tools/dotc/core/tasty/TastyFormat.scala | 6 ++- .../tools/dotc/core/tasty/TreePickler.scala | 1 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 + .../dotty/tools/dotc/parsing/Parsers.scala | 1 + .../src/dotty/tools/dotc/parsing/Tokens.scala | 44 ++++++++++--------- docs/docs/internals/syntax.md | 2 +- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 7b0e6b41e23a..cfdd2af56df2 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -122,6 +122,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 c7c30e871941..bd564ff98765 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -250,9 +250,14 @@ object Flags { final val AccessorOrSealed: FlagSet = Accessor.toCommonFlags - /** A mutable var */ + /** A mutable var */ final val Mutable: FlagSet = termFlag(12, "mutable") + /** An opqaue type */ + final val Opaque: FlagSet = typeFlag(12, "opaque") + + final val MutableOrOpaque: FlagSet = Mutable.toCommonFlags + /** Symbol is local to current class (i.e. private[this] or protected[this] * pre: Private or Protected are also set */ @@ -263,7 +268,7 @@ object Flags { */ final val ParamAccessor: FlagSet = termFlag(14, "") - /** A value or class implementing a module */ + /** A value or class implementing a module */ final val Module: FlagSet = commonFlag(15, "module") final val ModuleVal: FlagSet = Module.toTermFlags final val ModuleClass: FlagSet = Module.toTypeFlags @@ -439,7 +444,7 @@ object Flags { commonFlags(Private, Protected, Final, Case, Implicit, Override, JavaStatic) final val TypeSourceModifierFlags: FlagSet = - CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed + CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque final val TermSourceModifierFlags: FlagSet = CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased @@ -459,7 +464,7 @@ object Flags { final val FromStartFlags: FlagSet = Module | Package | Deferred | Method.toCommonFlags | HigherKinded.toCommonFlags | Param | ParamAccessor.toCommonFlags | - Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | + Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index a983bee8b9f7..0bb3a26b78af 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* ERASED LAZY OVERRIDE + OPAQUE INLINE MACRO // inline method containing toplevel splices INLINEPROXY // symbol of binding representing an inline parameter @@ -307,7 +308,8 @@ object TastyFormat { final val STABLE = 32 final val MACRO = 33 final val ERASED = 34 - final val PARAMsetter = 35 + final val OPAQUE = 35 + final val PARAMsetter = 36 // Cat. 2: tag Nat @@ -462,6 +464,7 @@ object TastyFormat { | INLINE | INLINEPROXY | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -519,6 +522,7 @@ object TastyFormat { case INLINE => "INLINE" case INLINEPROXY => "INLINEPROXY" 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 a4ca4a48e59f..2960c4ebc3f7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -646,6 +646,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) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 5c5bc7c76943..01ca47bdbecd 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -606,6 +606,7 @@ class TreeUnpickler(reader: TastyReader, case INLINE => addFlag(Inline) case INLINEPROXY => addFlag(InlineProxy) 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 e1fa29da0d07..ee86adf10df6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1744,6 +1744,7 @@ object Parsers { case PRIVATE => Mod.Private() case PROTECTED => Mod.Protected() case SEALED => Mod.Sealed() + case OPAQUE => Mod.Opaque() case IDENTIFIER if name == nme.INLINEkw => Mod.Inline() } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 66b235fdc9e4..6f425e53dd3e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -93,6 +93,7 @@ abstract class TokensCommon { //final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate //final val ENUM = 62; enter(ENUM, "enum") //final val ERASED = 63; enter(ERASED, "erased") + //final val OPAQUE = 64; enter(OPAQUE, "opaque") /** special symbols */ final val COMMA = 70; enter(COMMA, "','") @@ -152,7 +153,7 @@ object Tokens extends TokensCommon { final val BACKQUOTED_IDENT = 13; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident") - final val identifierTokens: BitSet = BitSet(IDENTIFIER, BACKQUOTED_IDENT) + final val identifierTokens: TokenSet = BitSet(IDENTIFIER, BACKQUOTED_IDENT) def isIdentifier(token : Int): Boolean = token >= IDENTIFIER && token <= BACKQUOTED_IDENT @@ -177,6 +178,7 @@ object Tokens extends TokensCommon { final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val ENUM = 62; enter(ENUM, "enum") final val ERASED = 63; enter(ERASED, "erased") + final val OPAQUE = 64; enter(OPAQUE, "opaque") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -197,52 +199,52 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords: TokenSet = tokenRange(IF, ERASED) + final val alphaKeywords: TokenSet = tokenRange(IF, OPAQUE) final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND) - final val keywords: BitSet = alphaKeywords | symbolicKeywords + final val keywords: TokenSet = alphaKeywords | symbolicKeywords final val allTokens: TokenSet = tokenRange(minToken, maxToken) - final val simpleLiteralTokens: BitSet = tokenRange(CHARLIT, STRINGLIT) | BitSet(TRUE, FALSE, SYMBOLLIT) - final val literalTokens: BitSet = simpleLiteralTokens | BitSet(INTERPOLATIONID, NULL) + final val simpleLiteralTokens: TokenSet = tokenRange(CHARLIT, STRINGLIT) | BitSet(TRUE, FALSE, SYMBOLLIT) + final val literalTokens: TokenSet = simpleLiteralTokens | BitSet(INTERPOLATIONID, NULL) - final val atomicExprTokens: BitSet = literalTokens | identifierTokens | BitSet( + final val atomicExprTokens: TokenSet = literalTokens | identifierTokens | BitSet( USCORE, NULL, THIS, SUPER, TRUE, FALSE, RETURN, XMLSTART) - final val canStartExpressionTokens: BitSet = atomicExprTokens | BitSet( + final val canStartExpressionTokens: TokenSet = atomicExprTokens | BitSet( LBRACE, LPAREN, QBRACE, QPAREN, IF, DO, WHILE, FOR, NEW, TRY, THROW) - final val canStartTypeTokens: BitSet = literalTokens | identifierTokens | BitSet( + final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet( THIS, SUPER, USCORE, LPAREN, AT) - final val canStartBindingTokens: BitSet = identifierTokens | BitSet(USCORE, LPAREN) + final val canStartBindingTokens: TokenSet = identifierTokens | BitSet(USCORE, LPAREN) - final val templateIntroTokens: BitSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) + final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) - final val dclIntroTokens: BitSet = BitSet(DEF, VAL, VAR, TYPE) + final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE) - final val defIntroTokens: BitSet = templateIntroTokens | dclIntroTokens + final val defIntroTokens: TokenSet = templateIntroTokens | dclIntroTokens - final val localModifierTokens: BitSet = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY, ERASED) + final val localModifierTokens: TokenSet = BitSet( + ABSTRACT, FINAL, SEALED, IMPLICIT, LAZY, ERASED, OPAQUE) - final val accessModifierTokens: BitSet = BitSet( + final val accessModifierTokens: TokenSet = BitSet( PRIVATE, PROTECTED) - final val modifierTokens: BitSet = localModifierTokens | accessModifierTokens | BitSet( + final val modifierTokens: TokenSet = localModifierTokens | accessModifierTokens | BitSet( OVERRIDE) - final val modifierTokensOrCase: BitSet = modifierTokens | BitSet(CASE) + final val modifierTokensOrCase: TokenSet = modifierTokens | BitSet(CASE) /** Is token only legal as start of statement (eof also included)? */ - final val mustStartStatTokens: BitSet = defIntroTokens | modifierTokens | BitSet(IMPORT, PACKAGE) + final val mustStartStatTokens: TokenSet = defIntroTokens | modifierTokens | BitSet(IMPORT, PACKAGE) - final val canStartStatTokens: BitSet = canStartExpressionTokens | mustStartStatTokens | BitSet( + final val canStartStatTokens: TokenSet = canStartExpressionTokens | mustStartStatTokens | BitSet( AT, CASE) - final val canEndStatTokens: BitSet = atomicExprTokens | BitSet( + final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet( TYPE, RPAREN, RBRACE, RBRACKET) - final val numericLitTokens: BitSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT) + final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index ef37c9214677..14eda9116940 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -291,7 +291,7 @@ LocalModifier ::= ‘abstract’ | ‘sealed’ | ‘implicit’ | ‘lazy’ - | ‘transparent’ + | ‘opaque’ | ‘inline’ | ‘erased’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] From 45c641c0d9e317d794c1196e3dd1bd41e23a66c5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:25:37 +0200 Subject: [PATCH 06/14] Desugaring and basic infrastructure for opaque types Desugaring, entering in Namer, and basic navigation operations for opaque types. Establishes opaque companions. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 45 +++++++++++- compiler/src/dotty/tools/dotc/ast/Trees.scala | 3 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 8 +-- .../src/dotty/tools/dotc/core/Flags.scala | 18 ++++- .../tools/dotc/core/SymDenotations.scala | 70 +++++++++++++++++-- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 + .../core/unpickleScala2/Scala2Unpickler.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 1 + .../src/dotty/tools/dotc/typer/Namer.scala | 46 +++++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +- 11 files changed, 175 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 2f937946cc71..14650d2febfa 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -695,6 +695,46 @@ object desugar { } } + /** Expand + * + * opaque type T = [Xs] => R + * + * to + * + * opaque type T = T.T + * synthetic object T { + * synthetic opaque type T >: [Xs] => R + * } + * + * The generated companion object will later (in Namer) be merged with the user-defined + * companion object, and the synthetic opaque type member will go into the self type. + */ + def opaqueAlias(tdef: TypeDef)(implicit ctx: Context): Tree = + if (tdef.rhs.isInstanceOf[TypeBoundsTree]) { + ctx.error(em"opaque type ${tdef.name} must be an alias type", tdef.pos) + tdef.withFlags(tdef.mods.flags &~ Opaque) + } + else { + def completeForwarder(fwd: Tree) = tdef.rhs match { + case LambdaTypeTree(tparams, tpt) => + val tparams1 = + for (tparam <- tparams) + yield tparam.withMods(tparam.mods | Synthetic) + lambdaAbstract(tparams1, + AppliedTypeTree(fwd, tparams.map(tparam => Ident(tparam.name)))) + case _ => + fwd + } + val moduleName = tdef.name.toTermName + val aliasType = cpy.TypeDef(tdef)( + rhs = completeForwarder(Select(Ident(moduleName), tdef.name))) + val localType = tdef.withFlags(Synthetic | Opaque) + val companions = moduleDef(ModuleDef( + moduleName, Template(emptyConstructor, Nil, EmptyValDef, localType :: Nil)) + .withFlags(Synthetic | Opaque)) + Thicket(aliasType :: companions.toList) + } + /** The name of `mdef`, after checking that it does not redefine a Scala core class. * If it does redefine, issue an error and return a mangled name instead of the original one. */ @@ -790,7 +830,10 @@ object desugar { 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 + case tree: TypeDef => + if (tree.isClassDef) classDef(tree) + else if (tree.mods.is(Opaque, butNot = Synthetic)) opaqueAlias(tree) + else tree case tree: DefDef => if (tree.name.isConstructorName) tree // was already handled by enclosing classDef else defDef(tree) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 8206e8dcd763..0e83edb8e49e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -350,7 +350,8 @@ object Trees { asInstanceOf[ThisTree[Untyped]] } - protected def setMods(mods: untpd.Modifiers): Unit = myMods = mods + /** Destructively update modifiers. To be used with care. */ + def setMods(mods: untpd.Modifiers): Unit = myMods = mods /** The position of the name defined by this definition. * This is a point position if the definition is synthetic, or a range position diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 71ea819f38a9..e30180edd145 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1102,9 +1102,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { private val InlinedCalls = new Property.Key[List[Tree]] /** Record an enclosing inlined call. - * EmptyTree calls (for parameters) cancel the next-enclosing call in the list instead of being added to it. - * We assume parameters are never nested inside parameters. - */ + * EmptyTree calls (for parameters) cancel the next-enclosing call in the list instead of being added to it. + * We assume parameters are never nested inside parameters. + */ override def inlineContext(call: Tree)(implicit ctx: Context): Context = { // We assume enclosingInlineds is already normalized, and only process the new call with the head. val oldIC = enclosingInlineds @@ -1118,7 +1118,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } /** All enclosing calls that are currently inlined, from innermost to outermost. - */ + */ def enclosingInlineds(implicit ctx: Context): List[Tree] = ctx.property(InlinedCalls).getOrElse(Nil) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index bd564ff98765..86d8f1c83ddb 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -526,7 +526,13 @@ object Flags { Accessor | AbsOverride | Stable | Captured | Synchronized | Erased /** Flags that can apply to a module class */ - final val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | ImplClass | Enum + final val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | + ImplClass | Enum | Opaque + + /** Flags that are copied from a synthetic companion to a user-defined one + * when the two are merged. See: Namer.mergeCompanionDefs + */ + final val RetainedSyntheticCompanionFlags: FlagSet = Opaque /** Packages and package classes always have these flags set */ final val PackageCreationFlags: FlagSet = @@ -639,6 +645,9 @@ object Flags { /** A Java companion object */ final val JavaModule: FlagConjunction = allOf(JavaDefined, Module) + /** An opaque companion object */ + final val OpaqueModule: FlagConjunction = allOf(Opaque, Module) + /** A Java companion object */ final val JavaProtected: FlagConjunction = allOf(JavaDefined, Protected) @@ -675,15 +684,18 @@ object Flags { /** Java symbol which is `protected` and `static` */ final val StaticProtected: FlagConjunction = allOf(JavaDefined, Protected, JavaStatic) + final val Scala2Trait: FlagConjunction = allOf(Scala2x, Trait) + final val AbstractFinal: FlagConjunction = allOf(Abstract, Final) final val AbstractSealed: FlagConjunction = allOf(Abstract, Sealed) + final val AbstractAndOverride: FlagConjunction = allOf(Abstract, Override) + final val SyntheticArtifact: FlagConjunction = allOf(Synthetic, Artifact) final val SyntheticModule: FlagConjunction = allOf(Synthetic, Module) final val SyntheticTermParam: FlagConjunction = allOf(Synthetic, TermParam) final val SyntheticTypeParam: FlagConjunction = allOf(Synthetic, TypeParam) final val SyntheticCase: FlagConjunction = allOf(Synthetic, Case) - final val AbstractAndOverride: FlagConjunction = allOf(Abstract, Override) - final val Scala2Trait: FlagConjunction = allOf(Scala2x, Trait) + final val SyntheticOpaque: FlagConjunction = allOf(Synthetic, Opaque) implicit def conjToFlagSet(conj: FlagConjunction): FlagSet = FlagSet(conj.bits) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index aa81140537af..09116d58e560 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -365,6 +365,23 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } + /** If this is a synthetic opaque type alias, mark it as Deferred with empty bounds + */ + 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 (isOpaqueHelper) { + info match { + case TypeAlias(alias) => + info = TypeBounds(defn.NothingType, abstractRHS(alias)) + setFlag(Deferred) + case _ => + } + } + } + // ------ Names ---------------------------------------------- /** The expanded name of this denotation. */ @@ -517,10 +534,19 @@ object SymDenotations { /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(implicit ctx: Context): Boolean = this is DeferredOrTypeParam + /** Is this symbol a user-defined opaque alias type? */ + def isOpaqueAlias(implicit ctx: Context): Boolean = is(Opaque, butNot = Synthetic) + + /** Is this symbol the companion of an opaque alias type? */ + def isOpaqueCompanion(implicit ctx: Context): Boolean = is(OpaqueModule) + + /** Is this symbol a synthetic opaque type inside an opaque companion object? */ + def isOpaqueHelper(implicit ctx: Context): Boolean = is(SyntheticOpaque, butNot = Module) + /** 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 + final def canHaveCompanion(implicit ctx: Context) = isClass || isOpaqueAlias /** Is this the denotation of a self symbol of some class? * This is the case if one of two conditions holds: @@ -830,10 +856,12 @@ object SymDenotations { /** The module implemented by this module class, NoSymbol if not applicable. */ final def sourceModule(implicit ctx: Context): Symbol = myInfo match { case ClassInfo(_, _, _, _, selfType) if this is ModuleClass => - selfType match { - case selfType: TermRef => selfType.symbol - case selfType: Symbol => selfType.info.asInstanceOf[TermRef].symbol + def sourceOfSelf(tp: Any): Symbol = tp match { + case tp: TermRef => tp.symbol + case tp: Symbol => sourceOfSelf(tp.info) + case tp: RefinedType => sourceOfSelf(tp.parent) } + sourceOfSelf(selfType) case info: LazyType => info.sourceModule case _ => @@ -954,6 +982,10 @@ object SymDenotations { */ final def companionModule(implicit ctx: Context): Symbol = if (is(Module)) sourceModule + else if (isOpaqueAlias) + info match { + case TypeAlias(TypeRef(TermRef(prefix, _), _)) => prefix.termSymbol + } else registeredCompanion.sourceModule private def companionType(implicit ctx: Context): Symbol = @@ -968,6 +1000,13 @@ object SymDenotations { final def companionClass(implicit ctx: Context): 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.suchThat(_.isOpaqueAlias).symbol + final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) else if (this.isClass) companionNamed(effectiveName.moduleClassName).sourceModule.moduleClass @@ -1017,6 +1056,24 @@ object SymDenotations { final def enclosingSubClass(implicit ctx: Context): Symbol = ctx.owner.ownersIterator.findSymbol(_.isSubClass(symbol)) + /** The alias of a synthetic opaque type that's stored in the self type of the + * containing object. + */ + def opaqueAlias(implicit ctx: Context): Type = { + if (isOpaqueHelper) { + owner.asClass.classInfo.selfType match { + case RefinedType(_, _, TypeBounds(lo, _)) => + def extractAlias(tp: Type): Type = tp match { + case OrType(alias, _) => alias + case HKTypeLambda(tparams, tp) => + HKTypeLambda(tparams.map(_.paramInfo), extractAlias(tp)) + } + extractAlias(lo) + } + } + 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 @@ -1653,7 +1710,8 @@ object SymDenotations { def computeTypeRef = { btrCache.put(tp, NoPrefix) - tp.symbol.denot match { + val tpSym = tp.symbol + tpSym.denot match { case clsd: ClassDenotation => def isOwnThis = prefix match { case prefix: ThisType => prefix.cls `eq` clsd.owner @@ -1661,7 +1719,7 @@ object SymDenotations { case _ => false } val baseTp = - if (tp.symbol eq symbol) + if (tpSym eq symbol) tp else if (isOwnThis) if (clsd.baseClassSet.contains(symbol)) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 09f8a3e36ee9..ba8e58dae6df 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3870,7 +3870,7 @@ object Types { extends CachedClassInfo(prefix, cls, Nil, decls, selfInfo) { /** Install classinfo with known parents in `denot` s */ - def finalize(denot: SymDenotation, parents: List[Type])(implicit ctx: Context): Unit = + def finalize(denot: SymDenotation, parents: List[Type], selfInfo: TypeOrSymbol)(implicit ctx: Context): Unit = denot.info = ClassInfo(prefix, cls, parents, decls, selfInfo) override def derivedClassInfo(prefix: Type)(implicit ctx: Context): ClassInfo = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 01ca47bdbecd..e910024e84de 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -814,6 +814,7 @@ class TreeUnpickler(reader: TastyReader, case _ => rhs.tpe.toBounds } sym.resetFlag(Provisional) + sym.normalizeOpaque() TypeDef(rhs) } case PARAM => diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index ffe4b6457756..d1297623da83 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -128,7 +128,7 @@ object Scala2Unpickler { else registerCompanionPair(scalacCompanion, denot.classSymbol) - tempInfo.finalize(denot, normalizedParents) // install final info, except possibly for typeparams ordering + tempInfo.finalize(denot, normalizedParents, ost) // install final info, except possibly for typeparams ordering denot.ensureTypeParamsInCorrectOrder() } } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 50e7789b2d5a..3e883b64f61a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -412,6 +412,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, butNot = (Synthetic | Module))) 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 22fbb882a441..e1f5e59e3259 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -502,7 +502,6 @@ class Namer { typer: Typer => case _ => } - def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context): Unit = tree match { case t: MemberDef if t.rawComment.isDefined => ctx.docCtx.foreach(_.addDocstring(sym, t.rawComment)) @@ -558,6 +557,7 @@ class Namer { typer: Typer => if (fromCls.mods.is(Synthetic) && !toCls.mods.is(Synthetic)) { removeInExpanded(fromStat, fromCls) val mcls = mergeModuleClass(toStat, toCls, fromCls.rhs.asInstanceOf[Template].body) + mcls.setMods(toCls.mods | (fromCls.mods.flags & RetainedSyntheticCompanionFlags)) moduleClsDef(fromCls.name) = (toStat, mcls) } @@ -617,12 +617,11 @@ class Namer { typer: Typer => 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, butNot = Synthetic)) { + if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef + else classDef(cdef.name) = cdef + } for (stat <- stats) expanded(stat) match { @@ -909,7 +908,7 @@ class Namer { typer: Typer => addAnnotations(denot.symbol) - val selfInfo = + val selfInfo: TypeOrSymbol = if (self.isEmpty) NoType else if (cls.is(Module)) { val moduleType = cls.owner.thisType select sourceModule @@ -937,7 +936,35 @@ class Namer { typer: Typer => ensureFirstIsClass(parents.map(checkedParentType(_)), cls.pos)) typr.println(i"completing $denot, parents = $parents%, %, parentTypes = $parentTypes%, %") - tempInfo.finalize(denot, parentTypes) + val finalSelfInfo: TypeOrSymbol = + if (cls.isOpaqueCompanion) { + // The self type of an opaque companion is refined with the type-alias of the original opaque type + def refineOpaqueCompanionSelfType(mt: Type, stats: List[Tree]): RefinedType = (stats: @unchecked) match { + case (td @ TypeDef(localName, rhs)) :: _ + if td.mods.is(SyntheticOpaque) && localName == name.stripModuleClassSuffix => + // create a context owned by the current opaque helper symbol, + // but otherwise corresponding to the context enclosing the opaque + // companion object, since that's where the rhs was defined. + val aliasCtx = ctx.outer.fresh.setOwner(symbolOfTree(td)) + val alias = typedAheadType(rhs)(aliasCtx).tpe + val original = cls.companionOpaqueType.typeRef + val cmp = ctx.typeComparer + val bounds = TypeBounds(cmp.orType(alias, original), cmp.andType(alias, original)) + RefinedType(mt, localName, bounds) + case _ :: stats1 => + refineOpaqueCompanionSelfType(mt, stats1) + } + selfInfo match { + case self: Type => + refineOpaqueCompanionSelfType(self, rest) + case self: Symbol => + self.info = refineOpaqueCompanionSelfType(self.info, rest) + self + } + } + else selfInfo + + tempInfo.finalize(denot, parentTypes, finalSelfInfo) Checking.checkWellFormed(cls) if (isDerivedValueClass(cls)) cls.setFlag(Final) @@ -1236,6 +1263,7 @@ class Namer { typer: Typer => tref.recomputeDenot() case _ => } + sym.normalizeOpaque() ensureUpToDate(sym.typeRef, dummyInfo) ensureUpToDate(sym.typeRef.appliedTo(tparamSyms.map(_.typeRef)), TypeBounds.empty) sym.info diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 36bab5e808c0..027641d1adb5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1587,7 +1587,11 @@ class Typer extends Namer val constr1 = typed(constr).asInstanceOf[DefDef] val parentsWithClass = ensureFirstTreeIsClass(parents mapconserve typedParent, cdef.namePos) val parents1 = ensureConstrCall(cls, parentsWithClass)(superCtx) - val self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible + var self1 = typed(self)(ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible + if (cls.isOpaqueCompanion) { + // this is necessary to ensure selftype is correctly pickled + self1 = tpd.cpy.ValDef(self1)(tpt = TypeTree(cls.classInfo.selfType)) + } if (self1.tpt.tpe.isError || classExistsOnSelf(cls.unforcedDecls, self1)) { // fail fast to avoid typing the body with an error type cdef.withType(UnspecifiedErrorType) From ca56c669bb8c1eb551d84d006590cb46c0955b75 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:31:28 +0200 Subject: [PATCH 07/14] Always compute asSeenFroms on opaque companion members For a member of an opaque companion C, it makes a difference whether the prefix is C or C.this - opaque aliases are visible for C.this but not for C. So we can never omit an asSeenFrom on these members. --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 5 +++-- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 09116d58e560..fda801f4091d 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -762,9 +762,10 @@ object SymDenotations { */ def membersNeedAsSeenFrom(pre: Type)(implicit ctx: Context): Boolean = !( this.isTerm - || this.isStaticOwner + || this.isStaticOwner && !this.isOpaqueCompanion || ctx.erasedTypes - || (pre eq NoPrefix) || (pre eq thisType) + || (pre eq NoPrefix) + || (pre eq thisType) ) /** Is this symbol concrete, or that symbol deferred? */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1677649d49ff..857b8e59ef59 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -53,7 +53,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. tp match { case tp: NamedType => val sym = tp.symbol - if (sym.isStatic || (tp.prefix `eq` NoPrefix)) tp + if (sym.isStatic && !sym.maybeOwner.isOpaqueCompanion || (tp.prefix `eq` NoPrefix)) tp else derivedSelect(tp, atVariance(variance max 0)(this(tp.prefix))) case tp: ThisType => toPrefix(pre, cls, tp.cls) From d0112da07f15ddf31f8ba88ab492fadab634075b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:32:59 +0200 Subject: [PATCH 08/14] Omit opaque aliases from realizability checking --- .../src/dotty/tools/dotc/core/CheckRealizable.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index bd4f77fae23a..774cc233bff8 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -105,7 +105,8 @@ class CheckRealizable(implicit ctx: Context) { /** `Realizable` if `tp` has good bounds, a `HasProblem...` instance * pointing to a bad bounds member otherwise. "Has good bounds" means: * - * - all type members have good bounds + * - all type members have good bounds (except for opaque helpers) + * - all refinements of the underlying type have good bounds (except for opaque companions) * - all base types are class types, and if their arguments are wildcards * they have good bounds. * - base types do not appear in multiple instances with different arguments. @@ -114,10 +115,15 @@ class CheckRealizable(implicit ctx: Context) { */ private def boundsRealizability(tp: Type) = { + def isOpaqueCompanionThis = tp match { + case tp: ThisType => tp.cls.isOpaqueCompanion + case _ => false + } + val memberProblems = for { mbr <- tp.nonClassTypeMembers - if !(mbr.info.loBound <:< mbr.info.hiBound) + if !(mbr.info.loBound <:< mbr.info.hiBound) && !mbr.symbol.isOpaqueHelper } yield new HasProblemBounds(mbr.name, mbr.info) @@ -126,7 +132,7 @@ class CheckRealizable(implicit ctx: Context) { name <- refinedNames(tp) if (name.isTypeName) mbr <- tp.member(name).alternatives - if !(mbr.info.loBound <:< mbr.info.hiBound) + if !(mbr.info.loBound <:< mbr.info.hiBound) && !isOpaqueCompanionThis } yield new HasProblemBounds(name, mbr.info) From 2268aa619da7fd6a0f817fd8163b3416ebbbec97 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:33:34 +0200 Subject: [PATCH 09/14] Consider opaque companions in implicit scope --- .../dotty/tools/dotc/typer/Implicits.scala | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 5b8a0aa4d152..54f0a22a3de6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -422,7 +422,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)) @@ -430,7 +430,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) @@ -468,21 +468,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.classSymbol)) 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.classSymbol))) + addRef(ref) + } + if (tp.widen.typeSymbol.isOpaqueAlias) addCompanionOf(tp.widen.typeSymbol) + else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) } From 646e596ab202110cc1b13557a0b0938e5604c011 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:34:54 +0200 Subject: [PATCH 10/14] Erasure of opaque types Type erasure is computed before and after opaque types are eliminated; we need a scheme that works in every case. --- .../dotty/tools/dotc/core/TypeErasure.scala | 79 +++++++++++-------- .../src/dotty/tools/dotc/core/Types.scala | 23 ++++++ 2 files changed, 69 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index f9e2d111ebc0..b334ca2b1316 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -8,6 +8,7 @@ import Uniques.unique import dotc.transform.ExplicitOuter._ import dotc.transform.ValueClasses._ import transform.TypeUtils._ +import Decorators._ import Definitions.MaxImplementedFunctionArity import scala.annotation.tailrec @@ -32,8 +33,8 @@ import scala.annotation.tailrec */ object TypeErasure { - private def erasureDependsOnArgs(tp: Type)(implicit ctx: Context) = - tp.isRef(defn.ArrayClass) || tp.isRef(defn.PairClass) + private def erasureDependsOnArgs(sym: Symbol)(implicit ctx: Context) = + sym == defn.ArrayClass || sym == defn.PairClass /** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and * isInstanceOf may have types that do not satisfy the predicate. @@ -46,7 +47,7 @@ object TypeErasure { case tp: TypeRef => val sym = tp.symbol sym.isClass && - !erasureDependsOnArgs(tp) && + !erasureDependsOnArgs(sym) && !defn.erasedToObject.contains(sym) && !defn.isSyntheticFunctionClass(sym) case _: TermRef => @@ -192,21 +193,32 @@ object TypeErasure { } } + /** Underlying type that does not contain aliases or abstract types + * at top-level, treating opaque aliases as transparent. + */ + def classify(tp: Type)(implicit ctx: Context): Type = + if (tp.typeSymbol.isClass) tp + else tp match { + case tp: TypeProxy => classify(tp.translucentSuperType) + case tp: AndOrType => tp.derivedAndOrType(classify(tp.tp1), classify(tp.tp2)) + case _ => tp + } + /** Is `tp` an abstract type or polymorphic type parameter that has `Any`, `AnyVal`, * or a universal trait as upper bound and that is not Java defined? Arrays of such types are * erased to `Object` instead of `Object[]`. */ def isUnboundedGeneric(tp: Type)(implicit ctx: Context): Boolean = tp.dealias match { - case tp: TypeRef => + case tp: TypeRef if !tp.symbol.isOpaqueHelper => !tp.symbol.isClass && - !tp.derivesFrom(defn.ObjectClass) && + !classify(tp).derivesFrom(defn.ObjectClass) && !tp.symbol.is(JavaDefined) case tp: TypeParamRef => - !tp.derivesFrom(defn.ObjectClass) && + !classify(tp).derivesFrom(defn.ObjectClass) && !tp.binder.resultType.isJavaMethod case tp: TypeAlias => isUnboundedGeneric(tp.alias) - case tp: TypeBounds => !tp.hi.derivesFrom(defn.ObjectClass) - case tp: TypeProxy => isUnboundedGeneric(tp.underlying) + case tp: TypeBounds => !classify(tp.hi).derivesFrom(defn.ObjectClass) + case tp: TypeProxy => isUnboundedGeneric(tp.translucentSuperType) case tp: AndType => isUnboundedGeneric(tp.tp1) && isUnboundedGeneric(tp.tp2) case tp: OrType => isUnboundedGeneric(tp.tp1) || isUnboundedGeneric(tp.tp2) case _ => false @@ -214,15 +226,15 @@ object TypeErasure { /** Is `tp` an abstract type or polymorphic type parameter, or another unbounded generic type? */ def isGeneric(tp: Type)(implicit ctx: Context): Boolean = tp.dealias match { - case tp: TypeRef => !tp.symbol.isClass + case tp: TypeRef if !tp.symbol.isOpaqueHelper => !tp.symbol.isClass case tp: TypeParamRef => true - case tp: TypeProxy => isGeneric(tp.underlying) + case tp: TypeProxy => isGeneric(tp.translucentSuperType) case tp: AndType => isGeneric(tp.tp1) || isGeneric(tp.tp2) case tp: OrType => isGeneric(tp.tp1) || isGeneric(tp.tp2) case _ => false } - /** The erased least upper bound is computed as follows + /** The erased least upper bound of two erased types is computed as follows * - if both argument are arrays of objects, an array of the erased lub of the element types * - if both arguments are arrays of same primitives, an array of this primitive * - if one argument is array of primitives and the other is array of objects, Object @@ -286,7 +298,8 @@ object TypeErasure { } } - /** The erased greatest lower bound picks one of the two argument types. It prefers, in this order: + /** The erased greatest lower bound of two erased type picks one of the two argument types. + * It prefers, in this order: * - arrays over non-arrays * - subtypes over supertypes, unless isJava is set * - real classes over traits @@ -317,7 +330,7 @@ object TypeErasure { * possible instantiations? */ def hasStableErasure(tp: Type)(implicit ctx: Context): Boolean = tp match { - case tp: TypeRef => + case tp: TypeRef if !tp.symbol.isOpaqueHelper => tp.info match { case TypeAlias(alias) => hasStableErasure(alias) case _: ClassInfo => true @@ -325,7 +338,7 @@ object TypeErasure { } case tp: TypeParamRef => false case tp: TypeBounds => false - case tp: TypeProxy => hasStableErasure(tp.superType) + case tp: TypeProxy => hasStableErasure(tp.translucentSuperType) case tp: AndType => hasStableErasure(tp.tp1) && hasStableErasure(tp.tp2) case tp: OrType => hasStableErasure(tp.tp1) && hasStableErasure(tp.tp2) case _ => false @@ -381,16 +394,17 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean tp case tp: TypeRef => val sym = tp.symbol - if (!sym.isClass) this(tp.info) + if (!sym.isClass) this(tp.translucentSuperType) else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp) else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type. else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym) else eraseNormalClassRef(tp) case tp: AppliedType => - if (tp.tycon.isRef(defn.ArrayClass)) eraseArray(tp) - else if (tp.tycon.isRef(defn.PairClass)) erasePair(tp) + val tycon = tp.tycon + if (tycon.isRef(defn.ArrayClass)) eraseArray(tp) + else if (tycon.isRef(defn.PairClass)) erasePair(tp) else if (tp.isRepeatedParam) apply(tp.underlyingIfRepeated(isJava)) - else apply(tp.superType) + else apply(tp.translucentSuperType) case _: TermRef | _: ThisType => this(tp.widen) case SuperType(thistpe, supertpe) => @@ -419,7 +433,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean case tp @ ClassInfo(pre, cls, parents, decls, _) => if (cls is Package) tp else { - def eraseParent(tp: Type) = tp.dealias match { + def eraseParent(tp: Type) = tp.dealias match { // note: can't be opaque, since it's a class parent case tp: AppliedType if tp.tycon.isRef(defn.PairClass) => defn.ObjectType case _ => apply(tp) } @@ -446,11 +460,9 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean private def eraseArray(tp: Type)(implicit ctx: Context) = { val defn.ArrayOf(elemtp) = tp - def arrayErasure(tpToErase: Type) = - erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase) - if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType) + if (classify(elemtp).derivesFrom(defn.NullClass)) JavaArrayType(defn.ObjectType) else if (isUnboundedGeneric(elemtp) && !isJava) defn.ObjectType - else JavaArrayType(arrayErasure(elemtp)) + else JavaArrayType(erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp)) } private def erasePair(tp: Type)(implicit ctx: Context): Type = { @@ -502,8 +514,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean // constructor method should not be semi-erased. else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp) else this(tp) - case AppliedType(tycon, _) if tycon.typeSymbol.isClass && !erasureDependsOnArgs(tycon) => - eraseResult(tycon) + case tp: AppliedType => + val sym = tp.tycon.typeSymbol + if (sym.isClass && !erasureDependsOnArgs(sym)) eraseResult(tp.tycon) + else this(tp) case _ => this(tp) } @@ -530,8 +544,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean } val sym = tp.symbol if (!sym.isClass) { - val info = tp.info - if (!info.exists) assert(false, "undefined: $tp with symbol $sym") + val info = tp.translucentSuperType + if (!info.exists) assert(false, i"undefined: $tp with symbol $sym") return sigName(info) } if (isDerivedValueClass(sym)) { @@ -543,10 +557,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean else normalizeClass(sym.asClass).fullName.asTypeName case tp: AppliedType => - sigName( - if (erasureDependsOnArgs(tp.tycon)) this(tp) - else if (tp.tycon.typeSymbol.isClass) tp.underlying - else tp.superType) + val sym = tp.tycon.typeSymbol + sigName( // todo: what about repeatedParam? + if (erasureDependsOnArgs(sym)) this(tp) + else if (sym.isClass) tp.underlying + else tp.translucentSuperType) case ErasedValueType(_, underlying) => sigName(underlying) case JavaArrayType(elem) => @@ -574,6 +589,4 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean println(s"no sig for $tp because of ${ex.printStackTrace()}") throw ex } - - } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ba8e58dae6df..3122e5f90217 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1526,6 +1526,9 @@ object Types { case TypeBounds(_, hi) => hi case st => st } + + /** Same as superType, except that opaque types are treated as transparent aliases */ + def translucentSuperType(implicit ctx: Context): Type = superType } // Every type has to inherit one of the following four abstract type classes., @@ -2214,6 +2217,14 @@ object Types { override def underlying(implicit ctx: Context): Type = info + override def translucentSuperType(implicit ctx: Context) = info match { + case TypeAlias(aliased) => aliased + case TypeBounds(_, hi) => + if (symbol.isOpaqueHelper) symbol.opaqueAlias.asSeenFrom(prefix, symbol.owner) + else hi + case _ => underlying + } + /** Hook that can be called from creation methods in TermRef and TypeRef */ def validated(implicit ctx: Context): this.type = { this @@ -2588,6 +2599,11 @@ object Types { def isAnd: Boolean def tp1: Type def tp2: Type + + def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context) = + if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this + else if (isAnd) AndType.make(tp1, tp2, checkValid = true) + else OrType.make(tp1, tp2) } abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType { @@ -3351,6 +3367,13 @@ object Types { cachedSuper } + override def translucentSuperType(implicit ctx: Context): Type = tycon match { + case tycon: TypeRef if tycon.symbol.isOpaqueHelper => + tycon.translucentSuperType.applyIfParameterized(args) + case _ => + superType + } + override def tryNormalize(implicit ctx: Context): Type = tycon match { case tycon: TypeRef => def tryMatchAlias = tycon.info match { From 10d36ee3e4309aa2e30b4322b25e2e9cc0fa0853 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:36:49 +0200 Subject: [PATCH 11/14] Eliminate opaque types This is done after RefChecks, so that we can check for proper overriding of opaque types. --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../src/dotty/tools/dotc/core/Periods.scala | 4 +- .../src/dotty/tools/dotc/core/Phases.scala | 7 +++ .../tools/dotc/transform/ElimOpaque.scala | 47 +++++++++++++++++++ .../tools/dotc/transform/FirstTransform.scala | 3 +- 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3bf3ea57d918..d96f25ea9b67 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -64,7 +64,8 @@ class Compiler { new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. new RefChecks) :: // Various checks mostly related to abstract members and overriding - List(new TryCatchPatterns, // Compile cases in try/catch + List(new ElimOpaque, // Turn opaque into normal aliases + new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts diff --git a/compiler/src/dotty/tools/dotc/core/Periods.scala b/compiler/src/dotty/tools/dotc/core/Periods.scala index 1976b8a171ad..be704dfcda8e 100644 --- a/compiler/src/dotty/tools/dotc/core/Periods.scala +++ b/compiler/src/dotty/tools/dotc/core/Periods.scala @@ -43,8 +43,8 @@ abstract class Periods { self: Context => val period = this.period period == p || period.runId == p.runId && - this.phases(period.phaseId).sameParentsStartId == - this.phases(p.phaseId).sameParentsStartId + this.phases(period.phaseId).sameBaseTypesStartId == + this.phases(p.phaseId).sameBaseTypesStartId } } diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index fef04082ca57..4f91adc45a54 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -320,6 +320,9 @@ object Phases { /** Can this transform change the parents of a class? */ def changesParents: Boolean = false + /** Can this transform change the base types of a type? */ + def changesBaseTypes: Boolean = changesParents + def exists: Boolean = true def initContext(ctx: FreshContext): Unit = () @@ -332,6 +335,7 @@ object Phases { private[this] var mySameMembersStartId = NoPhaseId private[this] var mySameParentsStartId = NoPhaseId + private[this] var mySameBaseTypesStartId = NoPhaseId /** The sequence position of this phase in the given context where 0 * is reserved for NoPhase and the first real phase is at position 1. @@ -351,6 +355,8 @@ object Phases { // id of first phase where all symbols are guaranteed to have the same members as in this phase final def sameParentsStartId: Int = mySameParentsStartId // id of first phase where all symbols are guaranteed to have the same parents as in this phase + final def sameBaseTypesStartId: Int = mySameBaseTypesStartId + // id of first phase where all symbols are guaranteed to have the same base tpyes as in this phase protected[Phases] def init(base: ContextBase, start: Int, end: Int): Unit = { if (start >= FirstPhaseId) @@ -363,6 +369,7 @@ object Phases { myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked mySameMembersStartId = if (changesMembers) id else prev.sameMembersStartId mySameParentsStartId = if (changesParents) id else prev.sameParentsStartId + mySameBaseTypesStartId = if (changesBaseTypes) id else prev.sameBaseTypesStartId } protected[Phases] def init(base: ContextBase, id: Int): Unit = init(base, id, id) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala b/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala new file mode 100644 index 000000000000..3d022d451833 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ElimOpaque.scala @@ -0,0 +1,47 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import dotty.tools.dotc.transform.MegaPhase._ +import ast.Trees._ +import ast.untpd +import Flags._ +import Types._ +import Constants.Constant +import Contexts.Context +import Symbols._ +import Decorators._ +import Annotations._ +import Annotations.ConcreteAnnotation +import Denotations.SingleDenotation +import SymDenotations.SymDenotation +import scala.collection.mutable +import DenotTransformers._ +import NameOps._ +import NameKinds.OuterSelectName +import StdNames._ + +object ElimOpaque { + val name: String = "elimOpaque" +} + +/** Rewrites opaque type aliases to normal alias types */ +class ElimOpaque extends MiniPhase with SymTransformer { + + override def phaseName: String = ElimOpaque.name + + // Override checks need to take place before treating opaque types as aliases + override def runsAfterGroupsOf: Set[String] = Set(typer.RefChecks.name) + + // base types of opaque aliases change + override def changesBaseTypes = true + + def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = + if (sym.isOpaqueHelper) { + sym.copySymDenotation( + info = TypeAlias(sym.opaqueAlias), + initFlags = sym.flags &~ (Opaque | Deferred)) + } + else sym +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index de660a9df6ec..6717b3499f05 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -22,10 +22,9 @@ object FirstTransform { val name: String = "firstTransform" } - /** The first tree transform * - eliminates some kinds of trees: Imports, NamedArgs - * - stubs out native and typelevel methods + * - stubs out native methods * - eliminates self tree in Template and self symbol in ClassInfo * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types From 6030cbbbe42c6ca883d7e0b6904ac209a2fb76da Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:40:54 +0200 Subject: [PATCH 12/14] Tests --- tests/neg/aliasing-subtypes.scala | 22 +++++ tests/neg/opaque-id.scala | 9 ++ tests/neg/opaque-self-encoding.scala | 1 - tests/neg/opaque.scala | 51 +++++++++++ tests/neg/tagging.scala | 53 ++++++++++++ tests/pending/pos/opaque-recursive.scala | 16 ++++ tests/pos/opaque-aliasing.scala | 42 +++++++++ tests/pos/opaque-digits.scala | 22 +++++ tests/pos/opaque-groups-params.scala | 50 +++++++++++ tests/pos/opaque-groups.scala | 104 +++++++++++++++++++++++ tests/pos/opaque-immutable-array.scala | 47 ++++++++++ tests/pos/opaque-nullable.scala | 26 ++++++ tests/pos/opaque-propability.scala | 43 ++++++++++ tests/pos/opaque.scala | 40 +++++++++ tests/pos/tagging.scala | 54 ++++++++++++ 15 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 tests/neg/aliasing-subtypes.scala create mode 100644 tests/neg/opaque-id.scala create mode 100644 tests/neg/opaque.scala create mode 100644 tests/neg/tagging.scala create mode 100644 tests/pending/pos/opaque-recursive.scala create mode 100644 tests/pos/opaque-aliasing.scala create mode 100644 tests/pos/opaque-digits.scala create mode 100644 tests/pos/opaque-groups-params.scala create mode 100644 tests/pos/opaque-groups.scala create mode 100644 tests/pos/opaque-immutable-array.scala create mode 100644 tests/pos/opaque-nullable.scala create mode 100644 tests/pos/opaque-propability.scala create mode 100644 tests/pos/opaque.scala create mode 100644 tests/pos/tagging.scala diff --git a/tests/neg/aliasing-subtypes.scala b/tests/neg/aliasing-subtypes.scala new file mode 100644 index 000000000000..09e761f2b6b1 --- /dev/null +++ b/tests/neg/aliasing-subtypes.scala @@ -0,0 +1,22 @@ +object Test { + + trait T { + type A + } + + class C extends T { this: m.type => // error: cannot instantiate + type A >: Int | Test.A <: Int & Test.A + + def reveal(x: A): Int = x + def inject(x: Int): A = x + + var a: A = ??? + x = a // OK! + a = x // OK! + } + + val m: T = new C + type A = m.A + + var x: A = ??? +} diff --git a/tests/neg/opaque-id.scala b/tests/neg/opaque-id.scala new file mode 100644 index 000000000000..434d7c1a5457 --- /dev/null +++ b/tests/neg/opaque-id.scala @@ -0,0 +1,9 @@ +object Test { + opaque type T[X] = X + object T { + def f(x: T[Int]): Int = x // OK + def g(x: Int): T[Int] = x // OK + } + val x: T[Int] = 2 // error + val y: Int = x // error +} diff --git a/tests/neg/opaque-self-encoding.scala b/tests/neg/opaque-self-encoding.scala index 62b7d5175d1d..2d295c8ec955 100644 --- a/tests/neg/opaque-self-encoding.scala +++ b/tests/neg/opaque-self-encoding.scala @@ -1,4 +1,3 @@ -trait TS { type TX = Int } trait TT { self: { type TX = Int } => type TX def lift(x: Int): TX = x diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala new file mode 100644 index 000000000000..7da4427b1c74 --- /dev/null +++ b/tests/neg/opaque.scala @@ -0,0 +1,51 @@ +object opaquetypes { + + opaque val x: Int = 1 // error + + opaque class Foo // error + + opaque type T // error + + opaque type U <: String // error + + opaque type Fix[F[_]] = F[Fix[F]] // error: cyclic // error + + opaque type O = String + + val s: O = "" // error + + object O { + val s: O = "" // should be OK + } + +} + +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/neg/tagging.scala b/tests/neg/tagging.scala new file mode 100644 index 000000000000..6ad97fde3632 --- /dev/null +++ b/tests/neg/tagging.scala @@ -0,0 +1,53 @@ +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 + } + + 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/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-aliasing.scala b/tests/pos/opaque-aliasing.scala new file mode 100644 index 000000000000..8edab1f6c6df --- /dev/null +++ b/tests/pos/opaque-aliasing.scala @@ -0,0 +1,42 @@ +object Test { + + opaque type A = Int + type AA = A + + var a: A = ??? + var aa: AA = ??? + + object A { + val x: A = a + a = x + val y: A = aa + aa = y + + type BB = A + val z1: BB = a + val z2: BB = aa + a = z1 + aa = z2 + } +} +object Test2 { + + opaque type A[X] = X + type AA[X] = A[X] + + var a: A[Int] = ??? + var aa: AA[Int] = ??? + + object A { + val x: A[Int] = a + a = x + val y: A[Int] = aa + aa = y + + type BB[X] = A[X] + val z1: BB[Int] = a + val z2: BB[Int] = aa + a = z1 + aa = z2 + } +} \ No newline at end of file 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-groups-params.scala b/tests/pos/opaque-groups-params.scala new file mode 100644 index 000000000000..7c3de6b146be --- /dev/null +++ b/tests/pos/opaque-groups-params.scala @@ -0,0 +1,50 @@ +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) + } + } + + // The following definition does not typecheck since the `First` + // parent argument refers to the outer `First`, not the synthetic inner one. + // See pos/opaque-groups.scala for a version that copmpiles. + opaque type First[A] = A + object First extends Wrapper[First] { // error: object creation impossible + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga // error: overriding + 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] { // error: object creation impossible + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga // error: overriding + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } +} \ No newline at end of file diff --git a/tests/pos/opaque-groups.scala b/tests/pos/opaque-groups.scala new file mode 100644 index 000000000000..547c1747c4cc --- /dev/null +++ b/tests/pos/opaque-groups.scala @@ -0,0 +1,104 @@ +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 { self => + type F[_] + 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 { + type F = 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 { + type F = 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 { + type F = 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 { + type F = 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 { + type F = 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 { + type F = 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 { + type F = 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 { + type F = 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-immutable-array.scala b/tests/pos/opaque-immutable-array.scala new file mode 100644 index 000000000000..711a8e0a61e8 --- /dev/null +++ b/tests/pos/opaque-immutable-array.scala @@ -0,0 +1,47 @@ +object ia { + import scala.reflect.ClassTag + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + object IArray { + def apply[A: ClassTag](xs: A*) = initialize(Array(xs: _*)) + + def initialize[A](body: => Array[A]): IArray[A] = body + def size[A](ia: IArray[A]): Int = ia.length + 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) + + assert(size(apply(1, 2, 3)) == 3) + assert(size(IArray(1, 2, 3)) == 3) + } + + // 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 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) + } +} diff --git a/tests/pos/opaque.scala b/tests/pos/opaque.scala new file mode 100644 index 000000000000..a1f2b09ad3bc --- /dev/null +++ b/tests/pos/opaque.scala @@ -0,0 +1,40 @@ +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 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 = apply(`this` + that) + } + + assert(exponent(LL) == 1.0) + } + + val LL: Logarithm = Logarithm(1) +} +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/pos/tagging.scala b/tests/pos/tagging.scala new file mode 100644 index 000000000000..6216b0b32364 --- /dev/null +++ b/tests/pos/tagging.scala @@ -0,0 +1,54 @@ +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 + } + + 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) + val ys1 = new Array[Double](20) +} From c8a5cd93b1245d6702e4c2c695f759e2c315c52d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 19 Oct 2018 23:41:52 +0200 Subject: [PATCH 13/14] Doc page --- docs/docs/reference/opaques.md | 56 ++++++++++++++++++++++++++++++++++ docs/sidebar.yml | 2 ++ 2 files changed, 58 insertions(+) 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..e4a2867049ef --- /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 396bd336ed34..5dfa6d052b2e 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -49,6 +49,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 From 93757f502960d527d9b8ebb6baa88dcd9fed3bc3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 21 Oct 2018 09:17:39 +0200 Subject: [PATCH 14/14] More tests --- tests/neg/OpaqueEscape.scala | 15 ++++++++ tests/neg/opaque-immutable-array.scala | 48 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/neg/OpaqueEscape.scala create mode 100644 tests/neg/opaque-immutable-array.scala diff --git a/tests/neg/OpaqueEscape.scala b/tests/neg/OpaqueEscape.scala new file mode 100644 index 000000000000..f88f0697ac68 --- /dev/null +++ b/tests/neg/OpaqueEscape.scala @@ -0,0 +1,15 @@ +object OpaqueEscape{ + opaque type Wrapped = Int + abstract class EscaperBase { + def unwrap(i:Wrapped):Int + def wrap(i:Int):Wrapped + } + class Escaper extends EscaperBase{ // error: needs to be abstract + override def unwrap(i:Int):Int = i // error overriding method unwrap + override def wrap(i:Int):Int = i // error overriding method wrap + } + + val e = new Escaper:EscaperBase + val w:Wrapped = e.wrap(1) + val u:Int = e.unwrap(w) +} \ No newline at end of file diff --git a/tests/neg/opaque-immutable-array.scala b/tests/neg/opaque-immutable-array.scala new file mode 100644 index 000000000000..5677b0fa9ebb --- /dev/null +++ b/tests/neg/opaque-immutable-array.scala @@ -0,0 +1,48 @@ +object ia { + + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + object IArray { + def initialize[A](body: => Array[A]): IArray[A] = body + def size[A](ia: IArray[A]): Int = ia.length + 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 + } + + def xs: IArray[Long] = ??? + + xs.length // error: not a member + xs.apply(2) // error: not a member + xs(1) = 0 // error: not a member +}