diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index ca21867fd711..5d8fe2a37d70 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -57,7 +57,7 @@ class GenBCode extends Phase { if (myOutput eq null) { val path = Directory(ctx.settings.outputDir.value) myOutput = - if (path.extension == "jar") JarArchive.create(path) + if (path.`extension` == "jar") JarArchive.create(path) else new PlainDirectory(path) } myOutput diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a9999c58f81e..5f579e963203 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -11,6 +11,7 @@ import language.higherKinds import typer.FrontEnd import collection.mutable.ListBuffer import util.Property +import config.Printers.desugr import reporting.diagnostic.messages._ import reporting.trace @@ -154,6 +155,25 @@ object desugar { ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit) } + private def desugarTypeBindings( + bindings: List[TypeDef], + forPrimaryConstructor: Boolean = false)(implicit ctx: Context): (List[TypeDef], List[ValDef]) = { + val epbuf = new ListBuffer[ValDef] + def desugarContextBounds(rhs: Tree): Tree = rhs match { + case ContextBounds(tbounds, cxbounds) => + epbuf ++= makeImplicitParameters(cxbounds, forPrimaryConstructor) + tbounds + case LambdaTypeTree(tparams, body) => + cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) + case _ => + rhs + } + val bindings1 = bindings mapConserve { tparam => + cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) + } + (bindings1, epbuf.toList) + } + /** Expand context bounds to evidence params. E.g., * * def f[T >: L <: H : B](params) @@ -171,21 +191,8 @@ object desugar { private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = { val DefDef(name, tparams, vparamss, tpt, rhs) = meth val mods = meth.mods - val epbuf = new ListBuffer[ValDef] - def desugarContextBounds(rhs: Tree): Tree = rhs match { - case ContextBounds(tbounds, cxbounds) => - epbuf ++= makeImplicitParameters(cxbounds, isPrimaryConstructor) - tbounds - case LambdaTypeTree(tparams, body) => - cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) - case _ => - rhs - } - val tparams1 = tparams mapConserve { tparam => - cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) - } - - val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList) + val (tparams1, evidenceParams) = desugarTypeBindings(tparams, isPrimaryConstructor) + val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), evidenceParams) /** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */ def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { @@ -293,6 +300,7 @@ object desugar { def isAnyVal(tree: Tree): Boolean = tree match { case Ident(tpnme.AnyVal) => true case Select(qual, tpnme.AnyVal) => isScala(qual) + case TypedSplice(tree) => tree.tpe.isRef(defn.AnyValClass) case _ => false } def isScala(tree: Tree): Boolean = tree match { @@ -749,6 +757,77 @@ object desugar { Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) } + /** extension id for : { } + * -> + * implicit class ($this: ) + * extends { + * import $this._ + * + * } + * + * where + * + * (, ) = desugarTypeBindings() + * = concatenated with in one clause + * = with each occurrence of unqualified `this` substituted by `$this`. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * extension for { } + * -> + * implicit class (private val $this: ) + * extends AnyVal { + * import $this._ + * + * } + * + * where + * + * = where each method definition gets as last parameter section. + */ + def extensionDef(tree: Extension)(implicit ctx: Context): Tree = { + val Extension(name, constr, extended, impl) = tree + val isSimpleExtension = impl.parents.isEmpty + + val firstParams = ValDef(nme.SELF, extended, EmptyTree).withFlags(Private | Local | ParamAccessor) :: Nil + val importSelf = Import(Ident(nme.SELF), Ident(nme.WILDCARD) :: Nil) + val body1 = importSelf :: substThis.transform(impl.body) + val impl1 = + if (isSimpleExtension) { + val (typeParams, evidenceParams) = + desugarTypeBindings(constr.tparams, forPrimaryConstructor = false) + cpy.Template(impl)( + constr = cpy.DefDef(constr)(tparams = typeParams, vparamss = firstParams :: Nil), + parents = ref(defn.AnyValType) :: Nil, + body = body1.map { + case ddef: DefDef => + def resetFlags(vdef: ValDef) = + vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) + val originalParams = constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) + case other => + other + }) + } + else + cpy.Template(impl)( + constr = cpy.DefDef(constr)(vparamss = firstParams :: constr.vparamss), + body = body1) + val mods1 = + if (isSimpleExtension) tree.mods + else tree.mods.withAddedMod(Mod.InstanceDcl()) + val icls = TypeDef(name, impl1).withMods(mods1 | Implicit) + desugr.println(i"desugar $tree --> $icls") + classDef(icls) + } + + private val substThis = new UntypedTreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case This(Ident(tpnme.EMPTY)) => Ident(nme.SELF).withPos(tree.pos) + case _ => super.transform(tree) + } + } + def defTree(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: ValDef => valDef(tree) case tree: TypeDef => if (tree.isClassDef) classDef(tree) else tree @@ -757,6 +836,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) + case tree: Extension => extensionDef(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 29096e44bc38..a755f9130e1c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1244,6 +1244,8 @@ object Trees { Stats.record("TreeAccumulator.foldOver total") def localCtx = if (tree.hasType && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx + def templateCtx = + ctx.handleOpaqueCompanion(ctx.fresh, ctx.owner) tree match { case Ident(name) => x @@ -1320,6 +1322,7 @@ object Trees { implicit val ctx = localCtx this(x, rhs) case tree @ Template(constr, parents, self, _) => + implicit val ctx = templateCtx this(this(this(this(x, constr), parents), self), tree.body) case Import(expr, selectors) => this(x, expr) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a3be000dbbc2..e4f1a96e63a4 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1017,8 +1017,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { /** A key to be used in a context property that tracks enclosing inlined calls */ private val InlinedCalls = new Property.Key[List[Tree]] - override def inlineContext(call: Tree)(implicit ctx: Context): Context = - ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + override def inlineContext(call: Tree)(implicit ctx: Context): Context = { + val ictx = ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) + def stopAt(owner: Symbol) = owner.is(Package) || ctx.owner.isContainedIn(owner) + (ictx /: call.symbol.ownersIterator.takeWhile(!stopAt(_)))(ctx.handleOpaqueCompanion) + } /** All enclosing calls that are currently inlined, from innermost to outermost */ def enclosingInlineds(implicit ctx: Context): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index f10670c42345..afd9cf35d89a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,6 +40,16 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } + /** extension name tparams vparamss for tpt impl + * + * where `tparams` and `vparamss` are part of `constr`. + */ + case class Extension(name: TypeName, constr: DefDef, tpt: Tree, impl: Template) + extends MemberDef{ + type ThisTree[-T >: Untyped] <: Trees.NameTree[T] with Trees.MemberDef[T] with Extension + def withName(name: Name)(implicit ctx: Context) = cpy.Extension(this)(name.toTypeName, constr, tpt, impl) + } + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -124,6 +134,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) @@ -137,6 +149,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Enum() extends Mod(Flags.EmptyFlags) case class EnumCase() extends Mod(Flags.EmptyFlags) + + case class InstanceDcl() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions @@ -409,6 +423,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: ModuleDef if (name eq tree.name) && (impl eq tree.impl) => tree case _ => finalize(tree, untpd.ModuleDef(name, impl)) } + def Extension(tree: Tree)(name: TypeName, constr: DefDef, tpt: Tree, impl: Template) = tree match { + case tree: Extension if (name eq tree.name) && (constr eq tree.constr) && (tpt eq tree.tpt) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Extension(name, constr, tpt, impl)) + } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree @@ -492,6 +510,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) + case Extension(name, constr, tpt, impl) => + cpy.Extension(tree)(name, transformSub(constr), transform(tpt), transformSub(impl)) case ParsedTry(expr, handler, finalizer) => cpy.ParsedTry(tree)(transform(expr), transform(handler), transform(finalizer)) case SymbolLit(str) => @@ -541,6 +561,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = tree match { case ModuleDef(name, impl) => this(x, impl) + case Extension(name, constr, tpt, impl) => + this(this(this(x, constr), tpt), impl) case ParsedTry(expr, handler, finalizer) => this(this(this(x, expr), handler), finalizer) case SymbolLit(str) => diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index bd8cb9844c0c..db540ebf85b5 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,6 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter + val desugr: Printer = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 6343341b29f4..750bec00989a 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -144,7 +144,7 @@ object Settings { Path(arg) match { case _: Directory => update(arg, args) - case p if p.extension == "jar" => + case p if p.`extension` == "jar" => update(arg, args) case _ => fail(s"'$arg' does not exist or is not a directory", args) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 95c5aa94dd37..e2a4968226bc 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -537,6 +537,7 @@ class Definitions { lazy val EqualsPatternClass = enterSpecialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType)) lazy val RepeatedParamClass = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType)) + lazy val OpaqueAliasAnnot = enterSpecialPolyClass(tpnme.OPAQUE_ALIAS, EmptyFlags, Seq(AnyType)) // fundamental classes lazy val StringClass = ctx.requiredClass("java.lang.String") @@ -1171,6 +1172,7 @@ class Definitions { AnyKindClass, RepeatedParamClass, ByNameParamClass2x, + OpaqueAliasAnnot, AnyValClass, NullClass, NothingClass, diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 9f88fdbc0cf3..ffaf821528e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -251,9 +251,14 @@ object Flags { final val AccessorOrSealed = Accessor.toCommonFlags - /** A mutable var */ + /** A mutable var */ final val Mutable = termFlag(12, "mutable") + /** An opqaue type */ + final val Opaque = typeFlag(12, "opaque") + + final val MutableOrOpaque = Mutable.toCommonFlags + /** Symbol is local to current class (i.e. private[this] or protected[this] * pre: Private or Protected are also set */ @@ -264,7 +269,7 @@ object Flags { */ final val ParamAccessor = termFlag(14, "") - /** A value or class implementing a module */ + /** A value or class implementing a module */ final val Module = commonFlag(15, "module") final val ModuleVal = Module.toTermFlags final val ModuleClass = Module.toTypeFlags @@ -439,15 +444,20 @@ object Flags { // --------- Combined Flag Sets and Conjunctions ---------------------- /** Flags representing source modifiers */ - final val SourceModifierFlags = - commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) + private val CommonSourceModifierFlags = + commonFlags(Private, Protected, Final, Case, Implicit, Override, JavaStatic) + + final val TypeSourceModifierFlags = + CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque + + final val TermSourceModifierFlags = + CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Erased /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local | - 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) @@ -457,7 +467,7 @@ object Flags { /** Flags guaranteed to be set upon symbol creation */ final val FromStartFlags = Module | Package | Deferred | MethodOrHKCommon | Param | ParamAccessor.toCommonFlags | - Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | + Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | Erroneous | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a6543572b23f..6458a0676a9b 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -143,8 +143,6 @@ object StdNames { val WHILE_PREFIX: N = "while$" val DEFAULT_EXCEPTION_NAME: N = "ex$" val INITIALIZER_PREFIX: N = "initial$" - val COMPANION_MODULE_METHOD: N = "companion$module" - val COMPANION_CLASS_METHOD: N = "companion$class" val BOUNDTYPE_ANNOT: N = "$boundType$" val QUOTE: N = "'" val TYPE_QUOTE: N = "type_'" @@ -191,6 +189,8 @@ object StdNames { final val EQUALS_PATTERN: N = "" final val LOCAL_CHILD: N = "" final val REPEATED_PARAM_CLASS: N = "" + final val OPAQUE_ALIAS: N = "" + final val LINKED_TYPE: N = "" final val WILDCARD_STAR: N = "_*" final val REIFY_TREECREATOR_PREFIX: N = "$treecreator" final val REIFY_TYPECREATOR_PREFIX: N = "$typecreator" @@ -247,6 +247,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val DEFAULT_CASE: N = "defaultCase$" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index c190dfb7262c..80c6873b5dca 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -380,6 +380,29 @@ object SymDenotations { case _ => unforcedDecls.openForMutations } + /** If this is an opaque type alias, mark it as Deferred with empty bounds + * while storing the former right-hand side in an OpaqueAlias annotation. + */ + final def normalizeOpaque()(implicit ctx: Context) = { + def abstractRHS(tp: Type): Type = tp match { + case tp: HKTypeLambda => tp.derivedLambdaType(resType = abstractRHS(tp.resType)) + case _ => defn.AnyType + } + if (is(Opaque)) { + info match { + case TypeAlias(alias) => + val companion = companionNamed(name.moduleClassName).sourceModule + val arg = + if (companion.exists) RefinedType(alias, nme.COMPANION, companion.termRef) + else alias + addAnnotation(Annotation(tpd.TypeTree(defn.OpaqueAliasAnnot.typeRef.appliedTo(arg)))) + info = TypeBounds(defn.NothingType, abstractRHS(alias)) + setFlag(Deferred) + case _ => + } + } + } + // ------ Names ---------------------------------------------- /** The expanded name of this denotation. */ @@ -486,15 +509,6 @@ object SymDenotations { final def isAnonymousModuleVal(implicit ctx: Context) = this.symbol.is(ModuleVal) && (initial.name startsWith str.ANON_CLASS) - /** Is this a companion class method or companion object method? - * These methods are generated by Symbols#synthesizeCompanionMethod - * and used in SymDenotations#companionClass and - * SymDenotations#companionModule . - */ - final def isCompanionMethod(implicit ctx: Context) = - name.toTermName == nme.COMPANION_CLASS_METHOD || - name.toTermName == nme.COMPANION_MODULE_METHOD - /** Is this a synthetic method that represents conversions between representations of a value class * These methods are generated in ExtensionMethods * and used in ElimErasedValueType. @@ -541,6 +555,11 @@ object SymDenotations { /** Is this symbol an abstract type or type parameter? */ final def isAbstractOrParamType(implicit ctx: Context) = this is DeferredOrTypeParam + /** Can this symbol have a companion module? + * This is the case if it is a class or an opaque type alias. + */ + final def canHaveCompanion(implicit ctx: Context) = isClass || is(Opaque) + /** Is this the denotation of a self symbol of some class? * This is the case if one of two conditions holds: * 1. It is the symbol referred to in the selfInfo part of the ClassInfo @@ -606,12 +625,9 @@ object SymDenotations { * - not an accessor * - not a label * - not an anonymous function - * - not a companion method */ final def isRealMethod(implicit ctx: Context) = - this.is(Method, butNot = AccessorOrLabel) && - !isAnonymousFunction && - !isCompanionMethod + this.is(Method, butNot = AccessorOrLabel) && !isAnonymousFunction /** Is this a getter? */ final def isGetter(implicit ctx: Context) = @@ -944,34 +960,49 @@ 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 if (is(Opaque)) + getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(ann) => + val AppliedType(_, arg :: Nil) = ann.tree.tpe + arg match { + case RefinedType(tp, _, ref) => ref.termSymbol + case _ => NoSymbol + } + case None => NoSymbol + } + 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 + + /** 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(_.is(Opaque)).symbol final def scalacLinkedClass(implicit ctx: Context): Symbol = if (this is ModuleClass) companionNamed(effectiveName.toTypeName) @@ -1022,6 +1053,21 @@ object SymDenotations { final def enclosingSubClass(implicit ctx: Context) = ctx.owner.ownersIterator.findSymbol(_.isSubClass(symbol)) + /** The alias of an opaque type */ + def opaqueAlias(implicit ctx: Context): Type = { + if (is(Opaque)) + getAnnotation(defn.OpaqueAliasAnnot) match { + case Some(ann) => + val AppliedType(_, arg :: Nil) = ann.tree.tpe + arg match { + case RefinedType(tp, nme.COMPANION, _) => tp + case tp => tp + } + case None => NoType + } + else NoType + } + /** The non-private symbol whose name and type matches the type of this symbol * in the given class. * @param inClass The class containing the result symbol's definition @@ -1234,6 +1280,7 @@ object SymDenotations { val annotations1 = if (annotations != null) annotations else this.annotations val d = ctx.SymDenotation(symbol, owner, name, initFlags1, info1, privateWithin1) d.annotations = annotations1 + d.registeredCompanion = registeredCompanion d } @@ -1799,6 +1846,16 @@ object SymDenotations { .copyCaches(this, phase.next) .installAfter(phase) } + + private[this] var myCompanion: Symbol = NoSymbol + + /** Register companion class */ + override def registerCompanion(companion: Symbol)(implicit ctx: Context) = + if (companion.canHaveCompanion && !unforcedIsAbsent && !companion.unforcedIsAbsent) + myCompanion = companion + + override def registeredCompanion(implicit ctx: Context) = { ensureCompleted(); myCompanion } + override def registeredCompanion_=(c: Symbol) = { myCompanion = c } } /** The denotation of a package class. diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 5b7a6b311ab2..2bae0b511a72 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -187,15 +187,6 @@ trait Symbols { this: Context => val companionMethodFlags = Flags.Synthetic | Flags.Private | Flags.Method - def synthesizeCompanionMethod(name: Name, target: SymDenotation, owner: SymDenotation)(implicit ctx: Context) = - if (owner.exists && target.exists && !owner.unforcedIsAbsent && !target.unforcedIsAbsent) { - val existing = owner.unforcedDecls.lookup(name) - - existing.orElse{ - ctx.newSymbol(owner.symbol, name, companionMethodFlags , ExprType(target.typeRef)) - } - } else NoSymbol - /** Create a package symbol with associated package class * from its non-info fields and a lazy type for loading the package's members. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e241a32e6a50..b8edb3d5d465 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -773,9 +773,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - if (tyconIsTypeRef) recur(tp1, tp2.superType) + if (followSuperType) recur(tp1, tp2.superType) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -784,12 +784,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case param2: TypeParamRef => isMatchingApply(tp1) || canConstrain(param2) && canInstantiate(param2) || - compareLower(bounds(param2), tyconIsTypeRef = false) + compareLower(bounds(param2), followSuperType = false) case tycon2: TypeRef => isMatchingApply(tp1) || { tycon2.info match { case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) + val gbounds2 = ctx.gadt.bounds(tycon2.symbol) + if (gbounds2 == null) compareLower(info2, followSuperType = true) + else compareLower(gbounds2 & info2, followSuperType = false) case info2: ClassInfo => val base = tp1.baseType(info2.cls) if (base.exists && base.ne(tp1)) @@ -821,8 +823,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } canConstrain(param1) && canInstantiate || isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow) - case tycon1: TypeRef if tycon1.symbol.isClass => - false + case tycon1: TypeRef => + !tycon1.symbol.isClass && { + val gbounds1 = ctx.gadt.bounds(tycon1.symbol) + if (gbounds1 == null) recur(tp1.superType, tp2) + else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2) + } case tycon1: TypeProxy => recur(tp1.superType, tp2) case _ => diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index a4566e729b72..2a668520f152 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -158,9 +158,6 @@ object TypeErasure { * - For $asInstanceOf : [T]T * - For $isInstanceOf : [T]Boolean * - For all abstract types : = ? - * - For companion methods : the erasure of their type with semiEraseVCs = false. - * The signature of these methods are used to keep a - * link between companions and should not be semi-erased. * - For Java-defined symbols: : the erasure of their type with isJava = true, * semiEraseVCs = false. Semi-erasure never happens in Java. * - For all other symbols : the semi-erasure of their types, with @@ -168,7 +165,7 @@ object TypeErasure { */ def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { val isJava = sym is JavaDefined - val semiEraseVCs = !isJava && !sym.isCompanionMethod + val semiEraseVCs = !isJava val erase = erasureFn(isJava, semiEraseVCs, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2f3e13c89847..ef5b321a8fa9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -263,7 +263,7 @@ object Types { } /** Is some part of this type produced as a repair for an error? */ - final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) + def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError, forceLazy = false) /** Does the type carry an annotation that is an instance of `cls`? */ @tailrec final def hasAnnotation(cls: ClassSymbol)(implicit ctx: Context): Boolean = stripTypeVar match { @@ -510,7 +510,7 @@ object Types { case tp: TypeRef => tp.denot match { case d: ClassDenotation => d.findMember(name, pre, excluded) - case d => go(d.info) + case d => goTypeRef(d) } case tp: AppliedType => tp.tycon match { @@ -521,7 +521,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) @@ -550,6 +550,11 @@ object Types { case _ => NoDenotation } + def goTypeRef(d: Denotation) = { + val mbr = go(d.info) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) + } def goRec(tp: RecType) = if (tp.parent == null) NoDenotation else { @@ -1290,6 +1295,23 @@ object Types { */ def deepenProto(implicit ctx: Context): Type = this + /** If this is a TypeRef or an Application of a GADT-bound type, replace the + * GADT reference by its upper GADT bound. Otherwise NoType. + */ + def followGADT(implicit ctx: Context): Type = widenDealias match { + case site: TypeRef if site.symbol.is(Opaque) => + ctx.gadt.bounds(site.symbol) match { + case TypeBounds(_, hi) => hi + case _ => NoType + } + case AppliedType(tycon, args) => + val tycon1 = tycon.followGADT + if (tycon1.exists) tycon1.appliedTo(args) + else NoType + case _ => + NoType + } + // ----- Substitutions ----------------------------------------------------- /** Substitute all types that refer in their symbol attribute to @@ -2116,7 +2138,13 @@ object Types { override def designator = myDesignator override protected def designator_=(d: Designator) = myDesignator = d - override def underlying(implicit ctx: Context): Type = info + override def underlying(implicit ctx: Context): Type = { + if (symbol.is(Opaque)) { + val gadtBounds = ctx.gadt.bounds(symbol) + if (gadtBounds != null) return gadtBounds + } + info + } } final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) { diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 2898db135a3b..efd48d0fa71c 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -164,10 +164,8 @@ class ClassfileParser( classInfo = parseAttributes(classRoot.symbol, classInfo) if (isAnnotation) addAnnotationConstructor(classInfo) - val companionClassMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, classRoot, moduleRoot) - if (companionClassMethod.exists) companionClassMethod.entered - val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, moduleRoot, classRoot) - if (companionModuleMethod.exists) companionModuleMethod.entered + classRoot.registerCompanion(moduleRoot.symbol) + moduleRoot.registerCompanion(classRoot.symbol) setClassInfo(classRoot, classInfo) setClassInfo(moduleRoot, staticInfo) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index f082765d6cb7..1d45123d1ebb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -183,6 +183,7 @@ Standard-Section: "ASTs" TopLevelStat* OVERRIDE INLINE // inline method MACRO // inline method containing toplevel splices + OPAQUE // opaque type STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait @@ -302,7 +303,8 @@ object TastyFormat { final val STABLE = 31 final val MACRO = 32 final val ERASED = 33 - final val PARAMsetter = 34 + final val OPAQUE = 34 + final val PARAMsetter = 35 // Cat. 2: tag Nat @@ -450,6 +452,7 @@ object TastyFormat { | OVERRIDE | INLINE | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -506,6 +509,7 @@ object TastyFormat { case OVERRIDE => "OVERRIDE" case INLINE => "INLINE" case MACRO => "MACRO" + case OPAQUE => "OPAQUE" case STATIC => "STATIC" case OBJECT => "OBJECT" case TRAIT => "TRAIT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f7d942b4bdad..d3714e210395 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -608,6 +608,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Trait) writeByte(TRAIT) if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) + if (flags is Opaque) writeByte(OPAQUE) } sym.annotations.foreach(pickleAnnotation(sym, _)) } @@ -618,8 +619,10 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot - // inline bodies are reconstituted automatically when unpickling + case _ => + val sym = ann.symbol + sym == defn.BodyAnnot || sym == defn.OpaqueAliasAnnot + // these are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index ddf3ef7ed765..c4ab9ca41efb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -539,6 +539,7 @@ class TreeUnpickler(reader: TastyReader, // avoids space leaks by not capturing the current context forkAt(rhsStart).readTerm() }) + sym.normalizeOpaque() goto(start) sym } @@ -574,6 +575,7 @@ class TreeUnpickler(reader: TastyReader, case OVERRIDE => addFlag(Override) case INLINE => addFlag(Inline) case MACRO => addFlag(Macro) + case OPAQUE => addFlag(Opaque) case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) case TRAIT => addFlag(Trait) @@ -747,12 +749,9 @@ class TreeUnpickler(reader: TastyReader, // The only case to check here is if `sym` is a root. In this case // `companion` might have been entered by the environment but it might // be missing from the Tasty file. So we check explicitly for that. - def isCodefined = - roots.contains(companion.denot) == seenRoots.contains(companion) - if (companion.exists && isCodefined) { - if (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 @@ -987,7 +986,7 @@ class TreeUnpickler(reader: TastyReader, def readLengthTerm(): Tree = { val end = readEnd() - def readBlock(mkTree: (List[Tree], Tree) => Tree): Tree = { + def readBlock(mkTree: (List[Tree], Tree) => Tree)(implicit ctx: Context): Tree = { val exprReader = fork skipTree() val stats = readStats(ctx.owner, end) @@ -1016,7 +1015,8 @@ class TreeUnpickler(reader: TastyReader, readBlock(Block) case INLINED => val call = readTerm() - readBlock((defs, expr) => Inlined(call, defs.asInstanceOf[List[MemberDef]], expr)) + val inlineCtx = tpd.inlineContext(call) + readBlock((defs, expr) => Inlined(call, defs.asInstanceOf[List[MemberDef]], expr))(inlineCtx) case IF => If(readTerm(), readTerm(), readTerm()) case LAMBDA => diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index f7dbda218e0e..e1361d513bb1 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -120,18 +120,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/fromtasty/Debug.scala b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala index f44ad7d63714..48abbf6de36b 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/Debug.scala @@ -36,7 +36,7 @@ object Debug { val fromTastyOut = Files.createDirectory(tmpOut.resolve("from-tasty")) val ext = "hasTasty" - val classes = Directory(fromSourcesOut).walk.filter(x => x.isFile && x.extension == ext).map { x => + val classes = Directory(fromSourcesOut).walk.filter(x => x.isFile && x.`extension` == ext).map { x => val source = x.toString // transform foo/bar/Baz.hasTasty into foo.bar.Baz source.substring(fromSourcesOut.toString.length + 1, source.length - ext.length - 1).replace('/', '.') diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9f86e7bde967..99fd6119a28a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1058,7 +1058,7 @@ object Parsers { * | PostfixExpr `match' `{' CaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] - * Ascription ::= `:' CompoundType + * Ascription ::= `:' InfixType * | `:' Annotation {Annotation} * | `:' `_' `*' */ @@ -1178,6 +1178,13 @@ object Parsers { t } + /** Ascription ::= `:' InfixType + * | `:' Annotation {Annotation} + * | `:' `_' `*' + * PatternAscription ::= `:' TypePattern + * | `:' `_' `*' + * TypePattern ::= RefinedType + */ def ascription(t: Tree, location: Location.Value) = atPos(startOffset(t)) { in.skipToken() in.token match { @@ -1539,7 +1546,7 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() } else Nil - /** Pattern1 ::= PatVar Ascription + /** Pattern1 ::= PatVar PatternAscription * | Pattern2 */ def pattern1(): Tree = { @@ -1649,6 +1656,7 @@ object Parsers { case PRIVATE => Mod.Private() case PROTECTED => Mod.Protected() case SEALED => Mod.Sealed() + case OPAQUE => Mod.Opaque() } /** Drop `private' modifier when followed by a qualifier. @@ -1830,8 +1838,10 @@ object Parsers { * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] + * ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’) + * ImplicitMods ::= `implicit` [`unused`] | `unused` `implicit` */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { + def paramClauses(owner: Name, ofCaseClass: Boolean = false, ofExtension: Boolean = false): List[List[ValDef]] = { var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once var firstClauseOfCaseClass = ofCaseClass @@ -1877,7 +1887,7 @@ object Parsers { } } def paramClause(): List[ValDef] = inParens { - if (in.token == RPAREN) Nil + if (!ofExtension && in.token == RPAREN) Nil else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -1890,7 +1900,8 @@ object Parsers { } } funArgMods() - + if (ofExtension && !imods.is(Implicit)) + syntaxError(i"parameters of extension must be implicit") commaSeparated(() => param()) } } @@ -1900,7 +1911,7 @@ object Parsers { imods = EmptyModifiers paramClause() :: { firstClauseOfCaseClass = false - if (imods is Implicit) Nil else clauses() + if (imods.is(Implicit) || ofExtension) Nil else clauses() } } else Nil } @@ -2158,6 +2169,7 @@ object Parsers { /** TmplDef ::= ([`case'] ‘class’ | trait’) ClassDef * | [`case'] `object' ObjectDef * | `enum' EnumDef + * | `extension' ExtensionDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { in.token match { @@ -2173,13 +2185,15 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => enumDef(start, mods, atPos(in.skipToken()) { Mod.Enum() }) + case EXTENSION => + extensionDef(start, posMods(start, mods)) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) EmptyTree } } - /** ClassDef ::= id ClassConstr TemplateOpt + /** ClassDef ::= id ClassConstr [TemplateClause] */ def classDef(start: Offset, mods: Modifiers): TypeDef = atPos(start, nameStart) { classDefRest(start, mods, ident().toTypeName) @@ -2187,7 +2201,7 @@ object Parsers { def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = { val constr = classConstr(name, isCaseClass = mods is Case) - val templ = templateOpt(constr) + val templ = templateClauseOpt(constr) TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start)) } @@ -2196,7 +2210,7 @@ object Parsers { def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val vparamss = paramClauses(owner, ofCaseClass = isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } @@ -2205,14 +2219,14 @@ object Parsers { def constrModsOpt(owner: Name): Modifiers = modifiers(accessModifierTokens, annotsAsMods()) - /** ObjectDef ::= id TemplateOpt + /** ObjectDef ::= id [TemplateClause] */ def objectDef(start: Offset, mods: Modifiers): ModuleDef = atPos(start, nameStart) { objectDefRest(start, mods, ident()) } def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = { - val template = templateOpt(emptyConstructor) + val template = templateClauseOpt(emptyConstructor) ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) } @@ -2222,7 +2236,7 @@ object Parsers { val modName = ident() val clsName = modName.toTypeName val constr = classConstr(clsName) - val impl = templateOpt(constr, isEnum = true) + val impl = templateClauseOpt(constr, isEnum = true, bodyRequired = true) TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) } @@ -2268,6 +2282,36 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } + /** ExtensionDef ::= id [ExtensionParams] 'for' AnnotType ExtensionClause + * ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + * ExtensionClause ::= [`:` Template] + * | [nl] `{` `def` DefDef {semi `def` DefDef} `}` + */ + def extensionDef(start: Offset, mods: Modifiers): Extension = atPos(start, nameStart) { + val name = ident().toTypeName + val tparams = typeParamClauseOpt(ParamOwner.Class) + val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) + val constr = makeConstructor(tparams, vparamss) + accept(FOR) + val extended = annotType() + val templ = + if (in.token == COLON) { + in.nextToken() + template(emptyConstructor, bodyRequired = true)._1 + } + else { + val templ = templateClauseOpt(emptyConstructor, bodyRequired = true) + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + templ + } + Extension(name, constr, extended, templ) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2284,26 +2328,30 @@ object Parsers { * @return a pair consisting of the template, and a boolean which indicates * whether the template misses a body (i.e. no {...} part). */ - def template(constr: DefDef, isEnum: Boolean = false): (Template, Boolean) = { + def template(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): (Template, Boolean) = { newLineOptWhenFollowedBy(LBRACE) if (in.token == LBRACE) (templateBodyOpt(constr, Nil, isEnum), false) else { val parents = tokenSeparated(WITH, constrApp) newLineOptWhenFollowedBy(LBRACE) - if (isEnum && in.token != LBRACE) + if (bodyRequired && in.token != LBRACE) syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) val missingBody = in.token != LBRACE (templateBodyOpt(constr, parents, isEnum), missingBody) } } - /** TemplateOpt = [`extends' Template | TemplateBody] + /** TemplateClause = `extends' Template | TemplateBody + * TemplateClauseOpt = [TemplateClause] */ - def templateOpt(constr: DefDef, isEnum: Boolean = false): Template = - if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum)._1 } + def templateClauseOpt(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): Template = + if (in.token == EXTENDS) { + in.nextToken() + template(constr, isEnum, bodyRequired)._1 + } else { newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) template(constr, isEnum)._1 + if (in.token == LBRACE || bodyRequired) template(constr, isEnum, bodyRequired)._1 else Template(constr, Nil, EmptyValDef, Nil) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 0cc0cc16fcea..e9e692d70c12 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -178,6 +178,8 @@ object Tokens extends TokensCommon { final val INLINE = 62; enter(INLINE, "inline") final val ENUM = 63; enter(ENUM, "enum") final val ERASED = 64; enter(ERASED, "erased") + final val OPAQUE = 65; enter(OPAQUE, "opaque") + final val EXTENSION = 66; enter(EXTENSION, "extension") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -198,7 +200,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, ERASED) + final val alphaKeywords = tokenRange(IF, EXTENSION) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -219,14 +221,14 @@ object Tokens extends TokensCommon { final val canStartBindingTokens = identifierTokens | BitSet(USCORE, LPAREN) - final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) + final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, EXTENSION, CASECLASS, CASEOBJECT) final val dclIntroTokens = BitSet(DEF, VAL, VAR, TYPE) final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED) + ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED, OPAQUE) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index 392d719cfefd..a398ab652aa5 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -13,7 +13,7 @@ import scala.language.implicitConversions class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = - annots.filter(_.tpe != defn.SourceFileAnnotType) + super.filterModTextAnnots(annots).filter(_.tpe != defn.SourceFileAnnotType) override protected def blockText[T >: Untyped](trees: List[Trees.Tree[T]]): Text = { super.blockText(trees.filterNot(_.isInstanceOf[Closure[_]])) @@ -35,8 +35,8 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def templateText(tree: TypeDef, impl: Template): Text = { val decl = - if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) - else modText(tree.mods &~ (Final | Module), keywordStr("object")) + if (!tree.mods.is(Module)) modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) + else modText(tree.mods &~ (Final | Module), keywordStr("object"), isType = false) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ "" } @@ -44,7 +44,7 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { import untpd.{modsDeco => _, _} dclTextOr(tree) { val printLambda = tree.symbol.isAnonymousFunction - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) provided (!printLambda) + val prefix = modText(tree.mods, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) provided (!printLambda) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt).provided(!printLambda) ~ optText(tree.rhs)((if (printLambda) " => " else " = ") ~ _) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48bc992f8a47..83532f865477 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -75,7 +75,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override protected def recursionLimitExceeded() = {} - protected val PrintableFlags = (SourceModifierFlags | Label | Module | Local).toCommonFlags + protected def PrintableFlags(isType: Boolean) = { + if (isType) TypeSourceModifierFlags | Module | Local + else TermSourceModifierFlags | Label | Module | Local + }.toCommonFlags override def nameString(name: Name): String = if (ctx.settings.YdebugNames.value) name.debugString else name.toString @@ -403,7 +406,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case tree @ TypeDef(name, rhs) => def typeDefText(tparamsText: => Text, rhsText: => Text) = dclTextOr(tree) { - modText(tree.mods, keywordStr("type")) ~~ (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ + modText(tree.mods, keywordStr("type"), isType = true) ~~ + (varianceText(tree.mods) ~ typeText(nameIdText(tree))) ~ withEnclosingDef(tree) { tparamsText ~ rhsText } } def recur(rhs: Tree, tparamsTxt: => Text): Text = rhs match { @@ -441,7 +445,19 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { - modText(tree.mods, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) + modText(tree.mods, keywordStr("object"), isType = false) ~~ + nameIdText(tree) ~ toTextTemplate(impl) + } + case tree @ Extension(name, constr, tpt, impl) => + withEnclosingDef(tree) { + modText(tree.mods, keywordStr("extension"), isType = false) ~~ + nameIdText(tree) ~ + { withEnclosingDef(constr) { + addVparamssText(tparamsText(constr.tparams), constr.vparamss.drop(1)) + } + } ~ + " for " ~ atPrec(DotPrec) { toText(tpt) } ~~ + toTextTemplateBody(impl, Str(" :") `provided` impl.parents.nonEmpty) } case SymbolLit(str) => "'" + str @@ -498,8 +514,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { t ~ cxBoundToText(cxb) } case PatDef(mods, pats, tpt, rhs) => - modText(mods, keywordStr("val")) ~~ toText(pats, ", ") ~ optAscription(tpt) ~ - optText(rhs)(" = " ~ _) + modText(mods, keywordStr("val"), isType = false) ~~ + toText(pats, ", ") ~ optAscription(tpt) ~ optText(rhs)(" = " ~ _) case ParsedTry(expr, handler, finalizer) => changePrec(GlobalPrec) { keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _) @@ -606,7 +622,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val")) ~~ + modText(tree.mods, keywordStr(if (tree.mods is Mutable) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ withEnclosingDef(tree) { optText(tree.rhs)(" = " ~ _) } } @@ -615,7 +631,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { import untpd.{modsDeco => _, _} dclTextOr(tree) { - val prefix = modText(tree.mods, keywordStr("def")) ~~ valDefText(nameIdText(tree)) + val prefix = modText(tree.mods, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) withEnclosingDef(tree) { addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ optText(tree.rhs)(" = " ~ _) @@ -624,17 +640,22 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = { - val Template(constr @ DefDef(_, tparams, vparamss, _, _), parents, self, _) = impl + val constr @ DefDef(_, tparams, vparamss, _, _) = impl.constr val tparamsTxt = withEnclosingDef(constr) { tparamsText(tparams) } val primaryConstrs = if (constr.rhs.isEmpty) Nil else constr :: Nil val prefix: Text = if (vparamss.isEmpty || primaryConstrs.nonEmpty) tparamsTxt else { - var modsText = modText(constr.mods, "") + var modsText = modText(constr.mods, "", isType = false) if (!modsText.isEmpty) modsText = " " ~ modsText if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this" withEnclosingDef(constr) { addVparamssText(tparamsTxt ~~ modsText, vparamss) } } + prefix ~ toTextTemplateBody(impl, keywordText(" extends") `provided` !ofNew, primaryConstrs) + } + + protected def toTextTemplateBody(impl: Template, leading: Text, leadingStats: List[Tree[_]] = Nil): Text = { + val Template(_, parents, self, _) = impl val parentsText = Text(parents map constrText, keywordStr(" with ")) val selfText = { val selfName = if (self.name == nme.WILDCARD) keywordStr("this") else self.name.toString @@ -652,13 +673,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { params ::: rest } else impl.body - val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}" + val bodyText = "{" ~~ selfText ~~ toTextGlobal(leadingStats ::: body, "\n") ~ "}" - prefix ~ (keywordText(" extends") provided !ofNew) ~~ parentsText ~~ bodyText + leading ~~ parentsText ~~ bodyText } protected def templateText(tree: TypeDef, impl: Template): Text = { - val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class")) + val decl = modText(tree.mods, keywordStr(if ((tree).mods is Trait) "trait" else "class"), isType = true) decl ~~ typeText(nameIdText(tree)) ~ withEnclosingDef(tree) { toTextTemplate(impl) } ~ (if (tree.hasType && ctx.settings.verbose.value) i"[decls = ${tree.symbol.info.decls}]" else "") } @@ -681,12 +702,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def annotText(tree: untpd.Tree): Text = "@" ~ constrText(tree) // DD - protected def modText(mods: untpd.Modifiers, kw: String): Text = { // DD + protected def modText(mods: untpd.Modifiers, kw: String, isType: Boolean): Text = { // DD val suppressKw = if (enclDefIsClass) mods is ParamAndLocal else mods is Param var flagMask = if (ctx.settings.YdebugFlags.value) AnyFlags - else if (suppressKw) PrintableFlags &~ Private - else PrintableFlags + else if (suppressKw) PrintableFlags(isType) &~ Private + else PrintableFlags(isType) if (homogenizedView && mods.flags.isTypeFlags) flagMask &~= Implicit // drop implicit from classes val flags = mods.flags & flagMask val flagsText = if (flags.isEmpty) "" else keywordStr((mods.flags & flagMask).toString) @@ -755,7 +776,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/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4be68b0879d4..cea2975da291 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -212,8 +212,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // Synthetic methods that are always present do not affect the API // and can therefore be ignored. - def alwaysPresent(s: Symbol) = - s.isCompanionMethod || (csym.is(ModuleClass) && s.isConstructor) + def alwaysPresent(s: Symbol) = csym.is(ModuleClass) && s.isConstructor val decls = cinfo.decls.filter(!alwaysPresent(_)) val apiDecls = apiDefinitions(decls) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 14bb0c00ceba..6671e81c0e81 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -15,6 +15,7 @@ import Contexts.Context import Symbols._ import SymDenotations._ import Decorators._ +import Annotations._ import dotty.tools.dotc.core.Annotations.ConcreteAnnotation import dotty.tools.dotc.core.Denotations.SingleDenotation import scala.collection.mutable @@ -34,13 +35,14 @@ object FirstTransform { * - eliminates some kinds of trees: Imports, NamedArgs * - stubs out native methods * - eliminates self tree in Template and self symbol in ClassInfo + * - rewrites opaque type aliases to normal alias types * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types * - drops branches of ifs using the rules * if (true) A else B ==> A * if (false) A else B ==> B */ -class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => +class FirstTransform extends MiniPhase with SymTransformer { thisPhase => import ast.tpd._ override def phaseName = FirstTransform.name @@ -57,16 +59,24 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => ctx } - /** eliminate self symbol in ClassInfo */ - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + /** Two transforms: + * 1. eliminate self symbol in ClassInfo + * 2. Rewrite opaque type aliases to normal alias types + */ + def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = sym.info match { case tp @ ClassInfo(_, _, _, _, self: Symbol) => - tp.derivedClassInfo(selfInfo = self.info) + sym.copySymDenotation(info = tp.derivedClassInfo(selfInfo = self.info)) + .copyCaches(sym, ctx.phase.next) case _ => - tp + if (sym.is(Opaque)) { + val result = sym.copySymDenotation(info = TypeAlias(sym.opaqueAlias)) + result.removeAnnotation(defn.OpaqueAliasAnnot) + result.resetFlag(Opaque | Deferred) + result + } + else sym } - override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 103afdad960a..c241a50fa563 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -238,9 +238,12 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase // In the case of macros we keep the call to be able to reconstruct the parameters that // are passed to the macro. This same simplification is applied in ReifiedQuotes when the // macro splices are evaluated. + def symTrace = + if (call.symbol.owner.companionOpaqueType.exists) call.symbol.owner + else call.symbol.topLevelClass val callTrace = if (call.symbol.is(Macro)) call - else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + else Ident(symTrace.typeRef).withPos(call.pos) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { diff --git a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala index b88d46fdda6a..e21dbc783b0f 100644 --- a/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala +++ b/compiler/src/dotty/tools/dotc/transform/RestoreScopes.scala @@ -44,18 +44,6 @@ class RestoreScopes extends MiniPhase with IdentityDenotTransformer { thisPhase val cls = tree.symbol.asClass val pkg = cls.owner.asClass - // Bring back companion links - val companionClass = cls.info.decls.lookup(nme.COMPANION_CLASS_METHOD) - val companionModule = cls.info.decls.lookup(nme.COMPANION_MODULE_METHOD) - - if (companionClass.exists) { - restoredDecls.enter(companionClass) - } - - if (companionModule.exists) { - restoredDecls.enter(companionModule) - } - pkg.enter(cls) val cinfo = cls.classInfo tree.symbol.copySymDenotation( diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index d8a98309a078..3873fbfc6fb4 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -123,15 +123,6 @@ class SymUtils(val self: Symbol) extends AnyVal { self } - def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { - if (!self.unforcedDecls.lookup(name).exists) { - val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) - if (companionMethod.exists) { - companionMethod.entered - } - } - } - /** If this symbol is an enum value or a named class, register it as a child * in all direct parent classes which are sealed. * @param @late If true, register only inaccessible children (all others are already diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2fc388373f12..e8274d2861a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -370,10 +370,7 @@ class TreeChecker extends Phase with SymTransformer { checkOwner(impl.constr) def isNonMagicalMethod(x: Symbol) = - x.is(Method) && - !x.isCompanionMethod && - !x.isValueClassConvertMethod && - !(x.is(Macro) && ctx.phase.refChecked) + x.is(Method) && !x.isValueClassConvertMethod && !(x.is(Macro) && ctx.phase.refChecked) val symbolsNotDefined = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 698fdaff0c15..6502d9b4bcbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -381,6 +381,7 @@ object Checking { checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`") if (sym.is(Inline)) checkApplicable(Inline, sym.isTerm && !sym.is(Mutable | Module)) if (sym.is(Lazy)) checkApplicable(Lazy, !sym.is(Method | Mutable)) + if (sym.is(Opaque)) checkApplicable(Opaque, sym.isAliasType) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index f11f75b704c4..f50360bdd212 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -394,7 +394,7 @@ trait ImplicitRunInfo { self: Run => override implicit protected val ctx: Context = liftingCtx override def stopAtStatic = true def apply(tp: Type) = tp match { - case tp: TypeRef if tp.symbol.isAbstractOrAliasType => + case tp: TypeRef if !tp.symbol.canHaveCompanion => val pre = tp.prefix def joinClass(tp: Type, cls: ClassSymbol) = AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner)) @@ -402,7 +402,7 @@ trait ImplicitRunInfo { self: Run => (lead /: tp.classSymbols)(joinClass) case tp: TypeVar => apply(tp.underlying) - case tp: AppliedType if !tp.tycon.typeSymbol.isClass => + case tp: AppliedType if !tp.tycon.typeSymbol.canHaveCompanion => def applyArg(arg: Type) = arg match { case TypeBounds(lo, hi) => AndType.make(lo, hi) case WildcardType(TypeBounds(lo, hi)) => AndType.make(lo, hi) @@ -440,21 +440,24 @@ trait ImplicitRunInfo { self: Run => case tp: NamedType => val pre = tp.prefix comps ++= iscopeRefs(pre) - def addClassScope(cls: ClassSymbol): Unit = { - def addRef(companion: TermRef): Unit = { - val compSym = companion.symbol - if (compSym is Package) - addRef(companion.select(nme.PACKAGE)) - else if (compSym.exists) - comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] - } - def addParentScope(parent: Type): Unit = - iscopeRefs(tp.baseType(parent.typeSymbol)) foreach addRef - val companion = cls.companionModule + def addRef(companion: TermRef): Unit = { + val compSym = companion.symbol + if (compSym is Package) + addRef(companion.select(nme.PACKAGE)) + else if (compSym.exists) + comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] + } + def addCompanionOf(sym: Symbol) = { + val companion = sym.companionModule if (companion.exists) addRef(companion.termRef) - cls.classParents foreach addParentScope } - tp.classSymbols(liftingCtx) foreach addClassScope + def addClassScope(cls: ClassSymbol): Unit = { + addCompanionOf(cls) + for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.typeSymbol))) + addRef(ref) + } + if (tp.widen.typeSymbol.is(Opaque)) addCompanionOf(tp.widen.typeSymbol) + else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 69836719c7d3..c68a084cfeb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -101,14 +101,31 @@ trait NamerContextOps { this: Context => if (owner.exists) freshCtx.setOwner(owner) else freshCtx } + /** If `owner` is a companion object of an opaque type, record the alias + * in the GADT bounds of `freshCtx. + */ + def handleOpaqueCompanion(freshCtx: FreshContext, owner: Symbol): FreshContext = { + if (owner.is(Module)) { + val opaq = owner.companionOpaqueType + val alias = opaq.opaqueAlias + if (alias.exists) { + val result = freshCtx.setFreshGADTBounds + result.gadt.setBounds(opaq, TypeAlias(alias)) + result + } + else freshCtx + } + else freshCtx + } + /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - val localCtx: Context = ctx.fresh.setNewScope + val localCtx: FreshContext = ctx.fresh.setNewScope selfInfo match { case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => localCtx.scope.openForMutations.enter(sym) case _ => } - localCtx + handleOpaqueCompanion(localCtx, ctx.owner) } def packageContext(tree: untpd.PackageDef, pkg: Symbol): Context = @@ -506,7 +523,6 @@ class Namer { typer: Typer => case _ => } - def setDocstring(sym: Symbol, tree: Tree)(implicit ctx: Context) = tree match { case t: MemberDef if t.rawComment.isDefined => ctx.docCtx.foreach(_.addDocstring(sym, t.rawComment)) @@ -613,22 +629,19 @@ 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 = { val classDef = mutable.Map[TypeName, TypeDef]() val moduleDef = mutable.Map[TypeName, TypeDef]() - def updateCache(cdef: TypeDef): Unit = { - if (!cdef.isClassDef || cdef.mods.is(Package)) return - - if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef - else classDef(cdef.name) = cdef - } + def updateCache(cdef: TypeDef): Unit = + if (cdef.isClassDef && !cdef.mods.is(Package) || cdef.mods.is(Opaque)) { + if (cdef.mods.is(ModuleClass)) moduleDef(cdef.name) = cdef + else classDef(cdef.name) = cdef + } for (stat <- stats) expanded(stat) match { @@ -842,6 +855,7 @@ class Namer { typer: Typer => addInlineInfo(denot) denot.info = typeSig(sym) Checking.checkWellFormed(sym) + denot.normalizeOpaque() denot.info = avoidPrivateLeaks(sym, sym.pos) } } @@ -929,7 +943,8 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.pos, - traitReq = parent ne parents.head, stablePrefixReq = true) + traitReq = (parent `ne` parents.head) || original.mods.hasMod[Mod.InstanceDcl], + stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { case Select(qual: Super, _) if ctx.scala2Mode => diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5aa4a2fbfd31..bd2982dbaf72 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -6,6 +6,7 @@ import core._ import ast._ import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps._, Symbols._ import NameKinds.DepParamName +import SymDenotations.NoDenotation import Trees._ import Constants._ import Scopes._ @@ -128,8 +129,11 @@ object ProtoTypes { memberProto.isRef(defn.UnitClass) || compat.normalizedCompatible(NamedType(tp1, name, m), memberProto) // Note: can't use `m.info` here because if `m` is a method, `m.info` - // loses knowledge about `m`'s default arguments. + // loses knowledge about `m`'s default arguments. || mbr match { // hasAltWith inlined for performance + case NoDenotation => + val tp2 = tp1.followGADT + tp2.exists && isMatchedBy(tp2) case mbr: SingleDenotation => mbr.exists && qualifies(mbr) case _ => mbr hasAltWith qualifies } @@ -318,6 +322,9 @@ object ProtoTypes { def isDropped: Boolean = toDrop + override def isErroneous(implicit ctx: Context): Boolean = + myTypedArgs.tpes.exists(_.widen.isErroneous) + override def toString = s"FunProto(${args mkString ","} => $resultType)" def map(tm: TypeMap)(implicit ctx: Context): FunProto = diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 61502cddf482..081c8c2130ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -233,21 +233,21 @@ trait TypeAssigner { */ def selectionType(site: Type, name: Name, pos: Position)(implicit ctx: Context): Type = { val mbr = site.member(name) - if (reallyExists(mbr)) site.select(name, mbr) - else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) { + if (reallyExists(mbr)) + site.select(name, mbr) + else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) TryDynamicCallType - } else { - if (site.isErroneous || name.toTermName == nme.ERROR) UnspecifiedErrorType - else { - 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 431721dc44e6..a8c627241595 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2114,8 +2114,11 @@ class Typer extends Namer noMatches } case alts => - val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) - errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType) + else { + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + } } } diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index d0155ace3711..748144c6164a 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -93,8 +93,8 @@ abstract class AbstractFile extends Iterable[AbstractFile] { def canonicalPath: String = if (jpath == null) path else jpath.normalize.toString /** Checks extension case insensitively. */ - def hasExtension(other: String) = extension == other.toLowerCase - private val extension: String = Path.extension(name) + def hasExtension(other: String) = `extension` == other.toLowerCase + private val `extension`: String = Path.`extension`(name) /** The absolute file, if this is a relative file. */ def absolute: AbstractFile @@ -122,7 +122,7 @@ abstract class AbstractFile extends Iterable[AbstractFile] { } /** Does this abstract file represent something which can contain classfiles? */ - def isClassContainer = isDirectory || (jpath != null && (extension == "jar" || extension == "zip")) + def isClassContainer = isDirectory || (jpath != null && (`extension` == "jar" || `extension` == "zip")) /** Create a file on disk, if one does not exist already. */ def create(): Unit diff --git a/compiler/src/dotty/tools/io/JarArchive.scala b/compiler/src/dotty/tools/io/JarArchive.scala index 0960160d52b7..f600cdd099a6 100644 --- a/compiler/src/dotty/tools/io/JarArchive.scala +++ b/compiler/src/dotty/tools/io/JarArchive.scala @@ -15,14 +15,14 @@ class JarArchive private (root: Directory) extends PlainDirectory(root) { object JarArchive { /** Create a new jar file. Overwrite if file already exists */ def create(path: Path): JarArchive = { - require(path.extension == "jar") + require(path.`extension` == "jar") path.delete() open(path, create = true) } /** Create a jar file. */ def open(path: Path, create: Boolean = false): JarArchive = { - require(path.extension == "jar") + require(path.`extension` == "jar") // creating a new zip file system by using the JAR URL syntax: // https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html diff --git a/compiler/src/dotty/tools/io/Path.scala b/compiler/src/dotty/tools/io/Path.scala index fbd2a4ac92c7..68bca86d54b4 100644 --- a/compiler/src/dotty/tools/io/Path.scala +++ b/compiler/src/dotty/tools/io/Path.scala @@ -35,10 +35,10 @@ import scala.util.Random.alphanumeric object Path { def isExtensionJarOrZip(jpath: JPath): Boolean = isExtensionJarOrZip(jpath.getFileName.toString) def isExtensionJarOrZip(name: String): Boolean = { - val ext = extension(name) + val ext = `extension`(name) ext == "jar" || ext == "zip" } - def extension(name: String): String = { + def `extension`(name: String): String = { var i = name.length - 1 while (i >= 0 && name.charAt(i) != '.') i -= 1 @@ -138,7 +138,7 @@ class Path private[io] (val jpath: JPath) { if (p isSame this) Nil else p :: p.parents } // if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg"), otherwise "" - def extension: String = { + def `extension`: String = { var i = name.length - 1 while (i >= 0 && name.charAt(i) != '.') i -= 1 @@ -148,17 +148,17 @@ class Path private[io] (val jpath: JPath) { } // compares against extensions in a CASE INSENSITIVE way. def hasExtension(ext: String, exts: String*) = { - val lower = extension.toLowerCase + val lower = `extension`.toLowerCase ext.toLowerCase == lower || exts.exists(_.toLowerCase == lower) } // returns the filename without the extension. - def stripExtension: String = name stripSuffix ("." + extension) + def stripExtension: String = name stripSuffix ("." + `extension`) // returns the Path with the extension. def addExtension(ext: String): Path = new Path(jpath.resolveSibling(name + ext)) // changes the existing extension out for a new one, or adds it // if the current path has none. def changeExtension(ext: String): Path = - if (extension == "") addExtension(ext) + if (`extension` == "") addExtension(ext) else new Path(jpath.resolveSibling(stripExtension + "." + ext)) // conditionally execute diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 2a7b78b6c64c..a1bffc4e640a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -60,6 +60,7 @@ class CompilationTests extends ParallelTesting { compileFile("tests/pos-special/i3589-b.scala", defaultOptions.and("-Xfatal-warnings")) + compileFile("tests/pos-special/i4166.scala", defaultOptions.and("-Xfatal-warnings")) + compileFile("tests/pos-special/i4185.scala", defaultOptions.and("-Xfatal-warnings")) + + compileFile("tests/pos-special/typeclass-encoding3.scala", defaultOptions.and("-Ykind-polymorphism")) + compileFile("tests/pos-special/completeFromSource/Test.scala", defaultOptions.and("-sourcepath", "tests/pos-special")) + compileFile("tests/pos-special/completeFromSource/Test2.scala", defaultOptions.and("-sourcepath", "tests/pos-special")) + compileFile("tests/pos-special/completeFromSource/Test3.scala", defaultOptions.and("-sourcepath", "tests/pos-special", "-scansource")) + diff --git a/compiler/test/dotty/tools/dotc/FromTastyTests.scala b/compiler/test/dotty/tools/dotc/FromTastyTests.scala index 75f2ed603242..00cf744b10e8 100644 --- a/compiler/test/dotty/tools/dotc/FromTastyTests.scala +++ b/compiler/test/dotty/tools/dotc/FromTastyTests.scala @@ -28,6 +28,7 @@ class FromTastyTests extends ParallelTesting { implicit val testGroup: TestGroup = TestGroup("posTestFromTasty") val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions, blacklist = Set( + "macro-deprecate-dont-touch-backquotedidents.scala", "t247.scala", diff --git a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala index bb8db2aff35c..c59e8fbb8782 100644 --- a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala +++ b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala @@ -88,6 +88,7 @@ class UserDefinedErrorMessages extends ErrorMessagesTest { checkMessagesAfter("frontend") { """ |class C { + | import language.implicitConversions | @annotation.implicitAmbiguous("msg A=${A}") | implicit def f[A](x: Int): String = "f was here" | implicit def g(x: Int): String = "f was here" diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index 53c6e0c53238..a961860f4c08 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -45,7 +45,7 @@ class PatmatExhaustivityTest { val reporter = TestReporter.simplifiedReporter(new PrintWriter(stringBuffer)) val files = Directory(path).list.toList - .filter(f => f.extension == "scala" || f.extension == "java" ) + .filter(f => f.`extension` == "scala" || f.`extension` == "java" ) .map(_.jpath.toString) try { @@ -69,7 +69,7 @@ class PatmatExhaustivityTest { @Test def patmatExhaustivity: Unit = { val res = Directory(testsDir).list.toList - .filter(f => f.extension == "scala" || f.isDirectory) + .filter(f => f.`extension` == "scala" || f.isDirectory) .map { f => if (f.isDirectory) compileDir(f.jpath) @@ -78,7 +78,7 @@ class PatmatExhaustivityTest { } val failed = res.filter { case (_, expected, actual) => expected != actual } - val ignored = Directory(testsDir).list.toList.filter(_.extension == "ignore") + val ignored = Directory(testsDir).list.toList.filter(_.`extension` == "ignore") failed.foreach { case (file, expected, actual) => println(s"\n----------------- incorrect output for $file --------------\n" + diff --git a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala index 66f42eb6e740..a9bcf8d738eb 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala @@ -26,7 +26,7 @@ object factories { type TypeTree = dotty.tools.dotc.ast.Trees.Tree[Type] def flags(t: Tree)(implicit ctx: Context): List[String] = - (t.symbol.flags & SourceModifierFlags) + (t.symbol.flags & (if (t.symbol.isType) TypeSourceModifierFlags else TermSourceModifierFlags)) .flagStrings.toList .filter(_ != "") .filter(_ != "interface") diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index a377abee83f8..4b532957bd35 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -109,6 +109,7 @@ ids ::= id {‘,’ id} Path ::= StableId | [id ‘.’] ‘this’ + | Path ‘.’ ‘common’ StableId ::= id | Path ‘.’ id | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id @@ -281,11 +282,13 @@ Binding ::= (id | ‘_’) [‘:’ Type] Modifier ::= LocalModifier | AccessModifier | ‘override’ + | ‘common’ LocalModifier ::= ‘abstract’ | ‘final’ | ‘sealed’ | ‘implicit’ | ‘lazy’ + | ‘opaque’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ (id | ‘this’) ‘]’ @@ -329,12 +332,18 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef | [‘case’] ‘object’ ObjectDef | ‘enum’ EnumDef -ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) + | ‘extension’ ExtensionDef +ClassDef ::= id ClassConstr [TemplateClause] ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] -ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor -EnumDef ::= id ClassConstr [‘extends’ [ConstrApps]] EnumBody EnumDef(mods, name, tparams, template) -TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] +ObjectDef ::= id [TemplateClause] ModuleDef(mods, name, template) // no constructor +EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody TypeDef(mods, name, template) +ExtensionDef ::= id [ExtensionParams] 'for' AnnotType ExtensionClause Extension(name, type, templ) +ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] +ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + +TemplateClause ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args) diff --git a/docs/docs/reference/dropped/existential-types.md b/docs/docs/reference/dropped/existential-types.md index 747a51060116..b0b05d46a899 100644 --- a/docs/docs/reference/dropped/existential-types.md +++ b/docs/docs/reference/dropped/existential-types.md @@ -29,4 +29,4 @@ of `Int`. When reading classfiles compiled with _scalac_, Dotty will do a best effort to approximate existential types with its own types. It will -issue a warning that a precise emulation is not possible. +issue a warning when a precise emulation is not possible. diff --git a/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index ac53adef5911..895e965dd97d 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -13,7 +13,7 @@ some terminology and notational conventions: - We use `E` as a name of an enum, and `C` as a name of a case that appears in `E`. - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance, - `` represents one or more a parameter lists `(...)` or nothing at all. + `` represents one or more parameter lists `(...)` or nothing at all. - Enum cases fall into three categories: @@ -64,7 +64,7 @@ map into case classes or vals. val C = $new(n, "C") - Here, `$new` is a private method that creates an instance of of `E` (see + Here, `$new` is a private method that creates an instance of `E` (see below). 4. If `E` is an enum with type parameters diff --git a/docs/docs/reference/extend/common-declarations.md b/docs/docs/reference/extend/common-declarations.md new file mode 100644 index 000000000000..15ae67cbc1be --- /dev/null +++ b/docs/docs/reference/extend/common-declarations.md @@ -0,0 +1,133 @@ +--- +layout: doc-page +title: "Common Declarations" +--- + +Typeclass traits can have `common` declarations. These are a way to +abstract over values that exist only once for each implementing type. +At first glance, `common` declarations resemble `static` definitions in a language like Java, +but they are more general since they can be inherited. + +As an example, consider the following trait `Text` with an implementation class `FlatText`. + +```scala +trait Text extends TypeClass { + def length: Int + def apply(idx: Int): Char + def concat(txt: This): This + def toStr: String + + common def fromString(str: String): This + common def fromStrings(strs: String*): This = + ("" :: strs).map(fromString).reduceLeft(_.concat) +} + +case class FlatText(str: String) extends Text { + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText): FlatText = FlatText(str ++ txt.str) + def toStr = str + + common def fromString(str: String) = FlatText(str) +} +``` + +The `common` method `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: + +```scala +val txt1 = FlatText.fromString("hello") +val txt2 = FlatText.fromStrings("hello", ", world") +``` + +`common` declarations only define members of the companion objects of classes, not traits. So the following would give a "member not found" error. + +```scala +val erroneous = Text.fromStrings("hello", ", world") // error: not found +``` + +The `common` definition of `fromString` in `FlatText` implicitly defines a member of +`object FlatText`. Alternatively, one could have defined it as a regular method in the +companion object. This is done in the following second implementation of `Text`, which +represents a text as a tree of strings. The two implementations share the definition of the `common` method `fromStrings` in `Text`. + +```scala +enum ConcText { + + case Str(s: String) + case Conc(t1: ConcText, t2: ConcText) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: Text) = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} +object ConcText { + def fromString(str: String): ConcText = Str(str) +} +``` + +## The `common` Reference + +Let's add another method to `Text`: +```scala +trait Text { + ... + def flatten: Instance = fromString(toStr) +} +``` +Why does this work? The `fromString` method is abstract in `Text` so how do we find the correct implementation in `flatten`? +Comparing with the `toStr` reference, that one is an instance method and therefore is expanded to `this.toStr`. But the same does not work for `fromString` because it is a `common` method, not an instance method. +In fact, the application above is syntactic sugar for + +```scala +this.common.fromString(this.toStr) +``` +The `common` selector is defined in each typeclass trait. It refers at runtime to +the object that implements the `common` declarations. + +### Relationship with Parameterization + +There are special rules for common declarations in parameterized traits or classes. Parameterized traits and classes can both contain common declarations, but they have different visibility rules. Common declarations in a trait do _not_ see the type parameters of the enclosing trait. So the following is illegal: + +```scala +trait T[A] { + /*!*/ common def f: T[A] // error: not found: A +} +``` + +On the other hand, common definitions in a class or an extension clause _can_ refer to the type parameters of that class. Consequently, actual type arguments have to be specified when accessing such a common member. Example: + +```scala +extension SetMonoid[T] for Set[T] : Monoid { + def add(that: Set[T]) = this ++ that + common def unit: Set[T] = Set() +} + +Set[Int].unit +Set[Set[String]].unit +``` + +**Note:** Common definitions in a parameterized class `C[T]` cannot be members of the companion object of `C` because that would lose the visibility of the type parameter `T`. Instead they are members of a separate class that is the result type of an `apply` method +in the companion object. For instance, the `SetMonoid` extension above would be expanded +along the following lines: + +```scala +object SetMonoid { + def apply[T] = new Monoid.Common { + def unit: Set[T] = Set() + } +} +``` +Then, as always, `Set[Int].unit` expands to `Set.apply[Int].unit`. diff --git a/docs/docs/reference/extend/common-translation.md b/docs/docs/reference/extend/common-translation.md new file mode 100644 index 000000000000..c5d2719bfdff --- /dev/null +++ b/docs/docs/reference/extend/common-translation.md @@ -0,0 +1,115 @@ + + +## Specification + +> + +## Translation + +Here is one possible translation of a trait `T` that defines `common` declarations `common D1, ..., common Dn` +and extends traits with common declarations `P1, ..., Pn` is as follows: +All `common` declarations are put in a trait `T.Common` which is defined in `T`'s companion object: + + object T { + trait Common extends P1.Common with ... with Pn.Common { self => + type Instance <: T { val `common`: self.type } + D1 + ... + Dn + } + } + +The trait inherits all `Common` traits associated with `T`'s parent traits. If no explicit definition of `Instance` is +given, it declares the `Instance` type as shown above. The trait `T` itself is expanded as follows: + + trait T extends ... { + val `common`: T.Common + import `common`._ + + ... + } + +Any direct reference to `x.common` in the body of `T` is simply translated to + + x.`common` + +The translation of a class `C` that defines `common` declarations `common D1, ..., common Dn` +and extends traits with common declarations `P1, ..., Pn` is as follows: +All `common` definitions of the class itself are placed in `C`'s companion object, which also inherits all +`Common` traits of `C`'s parents. If `C` already defines a companion object, the synthesized parents +come after the explicitly declared ones, whereas the common definitions precede all explicitly given statements of the +companion object. The companion object also defines the `Instance` type as + + type Instance = C + +unless an explicit definition of `Instance` is given in the same object. + +### Example: + +As an example, here is the translation of trait `Text` and its two implementations `FlatText` and `ConcText`: + +```scala +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Instance): Instance + def toStr: String + def flatten = `common`.fromString(toStr) +} +object Text { + trait Common { self => + type Instance <: Text { val `common`: self.type } + def fromString(str: String): Instance + def fromStrings(strs: String*): Instance = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val `common`: FlatText.type = FlatText + import `common`._ + + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + type Instance = FlatText + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val `common`: ConcText.type = ConcText + import `common`._ + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: ConcText): ConcText = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + type Instance = ConcText + def fromString(str: String) = Str(str) +} +``` + diff --git a/docs/docs/reference/extend/context-bounds.md b/docs/docs/reference/extend/context-bounds.md new file mode 100644 index 000000000000..581b9529c934 --- /dev/null +++ b/docs/docs/reference/extend/context-bounds.md @@ -0,0 +1,175 @@ +--- +layout: doc-page +title: "Context Bounds" +--- + +Context bounds present a new, general way to abstract over extensions, encompassing both class inheritance and instance declarations. Previously, a context +bound `[T : B]` required a parameterized class `B` as the bound of the type parameter `T`. +It specified an implicit evidence parameter of type `B[T]`. For instance, +```scala + def f[T : math.Ordering] +``` +was a shorthand form for +```scala + def f[T](implicit $ev: math.Ordering[T]) +``` + +This usage pattern is still supported. But there is now a second usage of context bounds which takes effect if the kind of the type parameter `T` and the bound `B` is the same. + +### Typeclass Bounds + +If `B` is a typeclass trait, a context bound `[T : B]` means "the implementation of typeclass `B` by type `T`". It translates to an implicit evidence parameter of type +`B.Impl[T]`. Example: + +```scala +def sum[T: Monoid](xs: Iterable[T]): T = + xs.foldLeft(Monoid.impl[T].unit)(_ `add` _) +``` + +This definition of `sum` can be used for all `Monoid` implementations, no matter whether they are defined by extending classes or extension clauses: + +```scala +assert( sum(List(1, 2, 3)) == 6 ) +assert( sum(List(Set(1, 2), Set(3))) == Set(1, 2, 3) ) +assert( sum(List(S(Z), S(S(Z)))) == List(S(S(S(Z)))) ) +``` + +Here is an equivalent formulation of `sum` which makes the implicit evidence parameter explicit: + +```scala +def sum[T](xs: Iterable[T])(implicit ev: Monoid.Impl[T]): T = + xs.foldLeft(ev.unit)(_ `add` _) +``` + +Of course, we could also have defined `sum` as an extension method: + +```scala +extension MonoidSum[T : Monoid] for Iterable[T] { + def sum = foldLeft(Monoid.impl[T].unit)(_ `add` _) +} +List(1, 2, 3).sum +``` + +### The `Impl` Type. + +The companion object of every typeclass trait `TC` defines a type constructor +```scala +object TC { + type Impl[T] = ... // (implementation dependent) +} +``` +The type `TC.Impl[T]` refers to the implementation object that implements `TC` for `T`. For instance, `Monoid.Impl[Int]` refers to the the `IntMonoid` object, whereas +`Monoid.Impl[Nat]` refers to the companion object of class `Nat`. In general, `TC.Impl[T]` is one of the following: + + - if the implementation of `C : TC` is via an extension clause, the object representing the extension + - if the implementation of `C : TC` is a class `C` extending `TC`, the companion object of `C`. + +Besides the `Impl` type, the companion object of a typeclass trait also defines a utility +method `impl`, specified as follows: +```scala + def impl[T](implicit ev: Impl[T]): Impl[T] = ev +``` +This method is used in the first implementation of `sum` above to retrieve +the monoid's `unit` value: +```scala +def sum[T: Monoid](xs: Iterable[T]): T = + xs.foldLeft(Monoid.impl[T].unit)(_ `add` _) +``` + +### Injectors + +This explains how `sum` can access `unit`, but what about `add`? The receiver type of `add` +is `Monoid`. How do we get an instance of this type from the implementation type `T`? + +In fact every `TC.Impl[T]` implicit also gives rise to an implicit conversion from `T` to +`TC`. This conversion can be inserted on the left-hand side argument of `add` (which is of type `T`) to produce a `Monoid` instance. (Implementations are free to optimize this, for instance by calling a binary version of `add` directly, which avoids the boxing receiver conversion). + +The details of the conversion are as follows: + + 1. The `scala` package defines an `Injector` trait as follows: + + ```scala + trait Injector { + /** The implementing type */ + type This + + /** The implemented trait */ + type $Instance + + /** The implementation via type `T` for this trait */ + implicit def inject(x: This): $Instance + } + ``` + + The `This` type of this injector is the same as the `This` type of an implementing typeclass trait; it represents the implementing type. The `$Instance` type is name-mangled, so user programs should not refer to it. It represents the implemented typeclass instance. The `inject` method converts from the first to the second. + + 2. Every implementation object `TC.Impl[T]` inherits an `Injector` instance where `This = T` and `$Instance <: TC`. Hence, it defines an `inject` conversion from `T` to `TC`. + + 3. There is a globally visible implicit conversion defined as follows: + + ```scala + implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type This = From }): ev.$Instance = + ev.inject(x) + ``` +Thus, the fully spelled-out body of the `sum` method is +```scala + def sum[T](xs: Iterable[T])(implicit ev: Monoid.Impl[T]): T = + xs.foldLeft(ev.unit)((x, y) => applyInjector(x)(ev).add(y)) +``` + +### Other Context Bounds + +Context bounds `[T : B]` can also be written if the bound `B` is not a typeclass trait. Still assuming that `T` and `B` have the same kind, the context bound then amounts to +specifying an implicit evidence of type `Injectable[T, B]`, where `scala.Injectable` is defined as follows: +```scala + type Injectable[T, +U] = Injector { type This = T; type $Instance <: U } +``` +Instance declarations of a non-typeclass trait `B` with an implementation type `I` generate implementation objects that inherit from `Injectable[I, B]`. The type bound `[I: B]` is then resolved as the implementation object, exactly in the same way as for typeclass traits. + +If class `C` extends `B` directly, the context bound `[C : B]` is resolved to `selfInject[C, B]`, referring to the following global definition of an injector that works for all pairs of types that are in a subtype relation: +```scala + def selfInject[U, T <: U]: Injectable[T, U] = new Injector { + type This = T + type $Instance = U + def inject(x: T) = x + } +``` +In other words, context bounds [T: B] for non-typeclass traits `B` work similar to the previous (now deprecated) view bounds `[T <% B]` in that they specify an implicit conversion from `T` to `B`. + +### Conditional Instance Declarations + +Context bounds enable conditional instance declarations. For instance, here is a type class +trait expressing an ordering: + +```scala +trait Ord extends TypeClass { + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + def <= (that: This) = compareTo(that) <= 0 + def >= (that: This) = compareTo(that) >= 0 +} +``` +Here is an instance declaration of `Ord` for `Int`: + +```scala +extension IntOrd for Int : Ord { + def compareTo(that: Int) = + if (this < that) -1 else if (this > that) +1 else 0 +} +``` + +And here is an instance declaration specifying that `List`s are ordered if their elements are: + +```scala +extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _ ) => -1 + case (_ , Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } +} +``` diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md new file mode 100644 index 000000000000..43bbceb1f063 --- /dev/null +++ b/docs/docs/reference/extend/extension-methods.md @@ -0,0 +1,146 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +extension CircleOps for Circle { + def circumference: Double = this.radius * math.Pi * 2 +} +``` + +The extension adds a method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Meaning of `this` + +Inside an extension method, the name `this` stands for the receiver on which the +method is applied when it is invoked. E.g. in the application of `circle.circumference`, +the `this` in the body of `circumference` refers to `circle`. As usual, `this` can be elided, so we could also have defined `CircleOps` like this: + +```scala +extension CircleOps for Circle { + def circumference: Double = radius * math.Pi * 2 +} +``` + +### Scope of Extensions + +Extensions can appear anywhere in a program; there is no need to co-define them with the types they extend. Extension methods are available wherever their defining extension is in scope. Extensions can be inherited or imported like normal definitions. + +### Extended Types + +An extension can add methods to arbitrary types. For instance, the following +clause adds a `longestStrings` extension method to a `Seq[String]`: + +```scala +extension StringOps for Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = map(_.length).max + filter(_.length == maxLength) + } +} +``` + +### Generic Extensions + +The previous example extended a specific instance of a generic type. It is also possible +to extend a generic type by adding type parameters to an extension: + +```scala +extension ListOps[T] for List[T] { + def second: T = tail.head +} +``` + +or: + + +```scala +extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +### Bounded Generic Extensions + +It is possible to use bounds for the type parameters of an extension. But subtype and context bounds are supported: + +```scala +extension ShapeListOps[T <: Shape] for List[T] { + def totalArea = map(_.area).sum +} + +extension OrdSeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 +} +``` + +### Implicit Parameters for Type Patterns + +The standard translation of context bounds expands the bound in the last example to an implicit _evidence_ parameter of type `math.Ordering[T]`. It is also possible to give evidence parameters explicitly. The following example is equivalent to the previous one: + +```scala +extension OrdSeqOps[T](implicit ev: math.Ordering[T]) for Seq[T] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +There can be only one parameter clause following a type pattern and it must be implicit. As usual, one can combine context bounds and implicit evidence parameters. + +### Toplevel Type Variables + +A type pattern consisting of a top-level type variable introduces a fully generic extension. For instance, the following extension introduces `x ~ y` as an alias +for `(x, y)`: + +```scala +extension InfixPair[T] for T { + def ~ [U](that: U) = (this, that) +} +``` + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + extension Ensuring[T] for T { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } +} +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } diff --git a/docs/docs/reference/extend/factored-instances.md b/docs/docs/reference/extend/factored-instances.md new file mode 100644 index 000000000000..11347d0069a4 --- /dev/null +++ b/docs/docs/reference/extend/factored-instances.md @@ -0,0 +1,76 @@ +--- +layout: doc-page +title: "Factored Instance Declarations" +--- + +Sometimes we want to re-use parts of one instance declaration in another. As an example, +let's flesh out the `Functor` - `Monad` example, adding a typeclass trait `Applicative` +for applicative functors in-between. + +```scala +trait Functor[A] extends TypeClass { + def map[B](f: A => B): This[B] +} + +trait Applicative[A] extends Functor[A] { + def map2[B, C](that: This[B])(f: (A, B) => C): This[C] + common def pure[B](x: B): This[B] + + def mapWith[B](f: This[A => B]): This[B] = map2(f)((x, f) => f(x)) + def map[B](f: A => B): This[B] = mapWith(pure(f)) +} + +trait Monad[A] extends Applicative[A] { + def flatMap[B](f: A => This[B]): This[B] +} +``` +`Applicative` can be extended in other ways as well. Here is `Traverse`, another common sub-trait of `Applicative`: + +```scala +trait Traverse[A] extends Applicative[A] { + def traverse[B, C[_]: Applicative](f: A => C[B]): C[This[B]] +} +``` + +The `List` type can be made an instance of both `Monad` and `Traverse` by writing an instance declaration for `List : Monad` and another one for `List : Traverse`. +However, these two declarations would then need to duplicate the definitions of the `Applicative` methods `map2` and `pure`. + +Factored instance declarations provide a way to avoid the duplication. The idea is to write three instance declarations, for `Applicative`, `Monad`, and `Traverse`: + +```scala +extension ListApplicative[A] for List : Applicative { + + def map[B](f: A => B): List[B] = this.map(f) + + def map2[B, C](that: List[B])(f: (A, B) => C): List[C] = + for (x <- this; y <- that) yield f(x, y) + + def pure[B]: List[B] = Nil +} + +extension ListMonad[A] for List : Monad { + + def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) +} + +extension ListTraverse[A] for List : Traverse { + + def traverse[B, C[_]: Applicative](f: A => C[B]): C[List[B]] = this match { + case Nil => Applicative.impl[C].pure[List[B]] + case x :: xs => f(x).map2(xs.traverse(f))(_ :: _) + } +} +``` +In the definitions above, `ListMonad` and `ListTraverse` lack definitions for `map2` and `pure`. These definitions are provided implicitly by forwarding to corresponding definitions in the `ListApplicative` instance declaration. If we had written the forwarders for `ListMonad` explicitly, this would look like the following, alternative definition: + +```scala +extension ListMonad[A] for List : Monad { + def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) + + // The following can be implicitly inserted: + def pure[B] = ListApplicative.pure[B] + def map2[B, C](that: List[B])(f: (A, B) => C): List[C] = + ListApplicative.inject(this).map2(that)(f) +} +``` +The last `map2` implementation looks like it requires an expensive object creation, but compilers or runtimes can usually optimize that away using inlining and escape analysis. \ No newline at end of file diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md new file mode 100644 index 000000000000..dcdc89e316db --- /dev/null +++ b/docs/docs/reference/extend/instance-declarations.md @@ -0,0 +1,62 @@ +--- +layout: doc-page +title: "Instance Declarations" +--- + +In addition to adding methods, an extension can also implement traits. Extensions implementing traits are also called _instance declarations_. For example, + +```scala +trait HasArea { + def area: Double +} + +extension CircleHasArea for Circle : HasArea { + def area = this.radius * this.radius * math.Pi +} +``` + +This extension makes `Circle` an instance of the `HasArea` trait. Specifically, it defines an implicit subclass of `HasArea` +which takes a `Circle` as argument and provides the given implementation. Hence, the implementation of the extension above would be like this + +```scala +implicit class CircleHasArea($this: Circle) extends HasArea { + def area = $this.radius * $this.radius * math.Pi +} +``` + +An instance definition can thus provide a kind of "implements" relationship that can be defined independently of the types it connects. + +### Generic Instance Declarations + +Just like extension methods, instance declarations can also be generic and their type parameters can have bounds. + +For example, assume we have the following two traits, which define binary and unary (infix) equality tests: + +```scala +trait Eql[T] { + def eql (x: T, y: T): Boolean +} + +trait HasEql[T] { + def === (that: T): Boolean +} +``` + +The following extension makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: + +```scala +extension HasEqlImpl[T : Eql] for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +### Rules for Instance Declarations + +An instance declaration can only implement a single trait, not a combination of traits, nor a class. Furthermore, +the implemented trait must be an interface trait according to the following definition: + +**Definition** An _interface trait_ is a trait that + - has only types and methods as members, + - does not have initialization statements, and + - has only interface traits as parents. + diff --git a/docs/docs/reference/extend/local-coherence.md b/docs/docs/reference/extend/local-coherence.md new file mode 100644 index 000000000000..21308f446b9b --- /dev/null +++ b/docs/docs/reference/extend/local-coherence.md @@ -0,0 +1,60 @@ +--- +layout: doc-page +title: "Local Coherence" +--- + +In the context of instance declarations, global coherence means that for every type `I` and typeclass trait `TC` there is never more than one way in which `I` implements `TC`. +Scala does not have a global coherence requirement for its implicits. Instead it reports an ambiguity error if an implicit search yields several different results. The advantage of Scala's approach lies in its flexibility, in particular its support for scoped implicits. But sometimes the ambiguity rules get in the way. Consider for instance the typeclass trait hierarchy presented in the last section, which involves `Functor`, `Applicative`, `Monad`, and `Traverse`. Now add a function like `transform` below that requires its parameter type +to be both a `Monad` and a `Traverse`. +```scala +def transform[F[_] : Monad : Traverse](x: F[A], f: A => A) = { ... x.map(f) ... } +``` +The call to `map` in the body of that function would be in error, because it is ambiguous - we don't know whether we should call the `map` method defined in `Monad` or +the one defined in `Traverse`. To see this in more detail, expand the context bounds in `transform` and add a type ascription for `x`: + +```scala +def transform[F[_], A](x: F[A], f: A => A)( + implicit + impl1: Monad.Impl[F], + impl2: Traversable.Impl[F]) + = { ... (x: Functor[A]).map(f) ... } +``` +There are two ways `x` can be converted to a `Functor[A]`, one through `impl1.inject` +the other through `impl2.inject`. Hence, an ambiguity error is reported. +This problem was brought up in issue #2029 and was presented +in a [paper at the Scala Symposium 2017](https://adelbertc.github.io/publications/typeclasses-scala17.pdf). + +Note that if we assume `List` as the actual implementation, this ambiguity is a false negative, since `List`'s implementation of `map` is the same for `Monad` and `Traverse`, +being defined in the common extension `ListApplicative`. But in general, a type might well have two different implementations for `map` (or `Functor` methods in general) when seen as +a `Monad` or as a `Traverse`. + +The (as yet tentative) idea to solve the problem is to provide a way to constrain implicit values to have common implementations for some of their super-traits. + +This can be done by introducing a predefined type constructor `Super[_]` as a member of all implementation objects of typeclass traits. If `impl` is an implementation object for type `I` and typeclass trait `T`, and `U` is a typeclass trait extended by `T`, then `impl.Super[U]` would give the type of the implementation object from which `impl` obtains all +implementations of `U`. For instance, assuming the factored instance declarations in the last section, +```scala + ListMonad.Super[Functor] = ListApplicative.type + ListTraverse.Super[Functor] = ListApplicative.type +``` +Indeed, all `Functor` methods defined by `List` are defined in `ListApplicative`, and inherited (i.e. implemented implicitly as forwarders) in both `ListMonad` and `ListTraverse`. +On the other hand, if we had not used factorization for `ListMonad`, +`ListMonad.Super[Functor]` would be `ListMonad.type`, since it would be `ListMonad` that +defines some of the methods in `Functor`. + +Once we have the `Super` type defined like this, we can add type constraints to enforce equalities. For instance: + +```scala +def transform[F[_], A](x: F[A], f: A => A)( + implicit + impl1: Monad.Impl[F], + impl2: Traversable.Impl[F], + impl1.Super[Functor] =:= impl2.Super[Functor]) + = { ... (x: Functor[A]).map(f) ... } +``` +Note that this construct involves parameter dependencies as implemented in #2079 - the third implicit parameter contains types that depend on the first two. + +The final piece of the puzzle is to make ambiguity checking take such constraints into account. More precisely, if searching for a injection from a type `T` to a typeclass +`U`, if there are two injectors `i1` and `i2` from `T` to `U`, but `i1.Super[U] =:= i2.Super[U]`, make an arbitrary choice instead of signaling an ambiguity. With this rule, the +`x.map(f)` call in the `transform` method typechecks. + +In effect we have replaced global coherence by local coherence - the ability to detect and require that two implementations implement a typeclass trait in the same way. \ No newline at end of file diff --git a/docs/docs/reference/extend/parameterized-typeclasses.md b/docs/docs/reference/extend/parameterized-typeclasses.md new file mode 100644 index 000000000000..700dd7438e85 --- /dev/null +++ b/docs/docs/reference/extend/parameterized-typeclasses.md @@ -0,0 +1,85 @@ +--- +layout: doc-page +title: "Parameterized Typeclass Traits" +--- + +Typeclass traits can be parameterized. Example: + +```scala +trait Functor[A] extends TypeClass { + def map[B](f: A => B): This[B] +} + +trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = flatMap(f.andThen(pure)) + + common def pure[A](x: A): This[A] +} +``` +This defines a parameterized typeclass trait `Functor`, extended by a parameterized typeclass trait `Monad`. + +In a parameterized typeclass trait, the kind of `This` is the same as the kind of the +trait. For instance, since `Functor` takes a single invariant type parameter, so does +`This` in `Functor`. Parameterized typeclass traits can only be extended by traits of the same kind. A sub-trait has to pass its type parameters to its super-trait +in the order they were defined: Examples: + +```scala +trait S[A, +B] extends TypeClass +trait T[X, +Y] extends S[X, Y] // OK + +/*!*/ trait T1[X, Y] extends S[X, Y] // error: wrong variance +/*!*/ trait T2[X, Y] extends S[Y, X] // error: wrong parameter order +/*!*/ trait T3[X, Y] extends S[X, Int] // error: not all parameters are passed +``` +These restrictions ensure that all participants in a typeclass trait hierarchy have the same notion of `This`. + +Other types are adapted analogously to higher-kinded types. For instance, `Monad` +defines the `Impl` type so that it takes a higher-kinded type parameter +```scala +type Impl[T[_]] +``` +and its `inject` has the signature given below. +```scala +def inject[A](x: This[A]): $Instance[A] +``` + +Here is an example of an instance declaration implementing a parameterized typeclass trait. It turns `List` into `Monad`. + +```scala +extension ListMonad[A] for List : Monad { + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + common def pure[A] = Nil +} +``` + +Implementations of higher-kinded typeclass traits have to have the same type parameters as the traits they implement, but they can also introduce new type parameters. New type parameters have to follow the type parameters passed along to the typeclass traits. Examples: + +```scala +trait T[X, +Y] { + def unit[A, B: This[A, B] +} +class C[X, +Y, Z] extends T[X, Y] // OK + +extension E1[X, +Y] for I[X, Y] : T[X, Y] // OK +extension E2[X, +Y, Z] for I[X, Y, Z] : T[X, Y] // OK +``` +Implementations are mapped to companions in the usual way after +subtracting any type parameters that are passed on to a parameterized typeclass traits. So the following three expressions would each select a `unit` value of trait `T`: +```scala + C[String].unit + E1.unit + E2[Int].unit +``` + +Context bounds also extend to higher-kinds. For instance, here is an extension method `flatten` for all types implementing the `Monad` trait: + +```scala +extension MonadFlatten[T[_] : Monad] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) +} +``` + diff --git a/docs/docs/reference/extend/rules.md b/docs/docs/reference/extend/rules.md new file mode 100644 index 000000000000..54198c064a4f --- /dev/null +++ b/docs/docs/reference/extend/rules.md @@ -0,0 +1,98 @@ +--- +layout: doc-page +title: "Syntax and Type Checking" +--- + +This section summarizes syntax changes and gives an outline of envisaged type checking rules. The type checking part is very provisional since details will probably depend on the +implemented encoding, which is not decided yet (see next section) + +## Syntax Changes + +The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + +New Keywords: + + extension common + +New Productions: + + TmplDef ::= ... + | ‘extension’ ExtensionDef + ExtensionDef ::= id [ExtensionParams] 'for' Type ExtensionClause + ExtensionParams ::= [ClsTypeParamClause] [[nl] ImplicitParamClause] + ExtensionClause ::= [`:` Template] + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + + Modifier ::= ... + | ‘common’ + Path ::= ... + | Path ‘.’ ‘common’ + + +## Type Checking Outline + +Here's an outline of the type checking rules and the auxiliary definitions implicitly added to programs. To keep things simple, this section only talks about the first-order case. +The section on "Parameterized Typeclass Traits" talks a bit how to extend this to higher-kinds, and the[typeclass encoding test case](https://github.com/dotty-staging/dotty/blob/add-common/tests/pos/typeclass-encoding3.scala) provides further examples. + +### TypeClass Traits + +There is a new marker trait `scala.TypeClass`. Traits and classes inheriting directly or indirectly from `TypeClass` are called _typeclass traits_. They differ as follows from normal traits: + + - They can have `common` definitions + - They can refer to the type `This` + +The companion object of a typeclass trait `TC` contains two compiler-generated members + + - A type `Impl[T]` mapping an implementation type `T` of the trait to the type of the + corresponding implementation object, which coincides with `T`'s `common` part. + + - A method `impl[T]` returning the implementation object of `T` for `TC`. + +### The `This` Type + +The `This` type is modeled as an abstract type. It is as if each typeclass trait +was augmented with the following definition: + + common type This + +Typeclass traits have an implicit conversion in scope that converts `This` to an +instance of the typeclass trait. + +### Rules for Common + +Common definitions cannot see instance members of their enclosing trait. Common definitions in classes (but not traits) _can_ see the type parameters of the enclosing class. + +A Typeclass trait contains an instance member `common` that refers to the object containing the `common` members of the trait. If `p` refers to a typeclass trait, then `p.common` refers to this object. + +### Typeclass Implementation + +An implementation of a typeclass trait is a class or object that extends the trait (possibly that implementation is generated from an extension clause). +An implementation `C` fixes the type of `This` to + + type This = C + +unless `C` extends another implementation class `B` (in this case `B` has already defined `This`.) + +### Injections + +There is a new standard type `scala.Injectable[T, U]` that allows to abstract over implicit conversions similar to how `scala.<:<` abstracts over subtyping. The `Impl` types +in typeclass traits inherit this type. An extension clause +```scala +extension X for C : T { ... } +``` +creates an instance of `Injectable[C, T]` (which is also an `Impl` in the case where `T` is a typeclass trait). There is a standard summon of `Injectable[T, U]` if `T` is a subtype of `U`. + +Finally, there is a global implicit conversion that uses `Injectable`: + +```scala + implicit def $inject[From, To](x: From)(implicit ev: Injectable[From, To]: To +``` + +### Context Bounds + +A context bound `[T: B]` expands to an implicit evidence parameter of one of the following types: + + - If `B` has one parameter more than `T`: `B[T]` (this is the previous meaning of context bounds) + - Otherwise, if `B` is a typeclass trait: `B.Impl[T]` + - Otherwise, if `B` is a standard trait: `Injectable[T, B]` + diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md new file mode 100644 index 000000000000..67cb8ff7cd4a --- /dev/null +++ b/docs/docs/reference/extend/translation.md @@ -0,0 +1,97 @@ +--- +layout: doc-page +title: "Translation of Extensions" +--- + +**Note:** This section gives an explanation how extension methods and instance declarations could be mapped to value classes and implicit classes. It does not consider typeclass traits. Given that value classes and implicit classes are supposed to be replaced by +extension methods and instance declarations, it would be good to present a translation that bypasses value classes and that also extends to typeclass traits. There are several +possibilities for such a translation. We should to experiment further to clarify which +scheme to prefer and flesh it out in detail. + +Here are links to two possible encodings: + + - A test in this PR: https://github.com/dotty-staging/dotty/blob/add-common/tests/pos/typeclass-encoding3.scala + - An alternative encoding: https://gist.github.com/OlivierBlanvillain/d314ddbcb640e2ce5604d860b5073008. + +--- + +Extensions are closely related to implicit classes and can be translated into them. In short, +an extension that just adds extension methods translates into an implicit value class whereas an instance declaration translates into a regular implicit class. The following sections sketch this translation. + +Conversely, it is conceivable (and desirable) to replace most usages of implicit classes and value classes by extensions and [opaque types](../opaques.html). We plan to [drop](../dropped/implicit-value-classes.html) +these constructs in future versions of the language. Once that is achieved, the translations described +below can be simply composed with the existing translations of implicit and value classes into the core language. It is +not necessary to retain implicit and value classes as an intermediate step. + + +### Translation of Extension Methods + +Assume an extension + + extension for { } + +where both `` and `` can be absent. +For simplicity assume that there are no context bounds on any of the type parameters +in ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence parameters in ``. + +The extension is translated to the following implicit value class: + + implicit class (private val $this: ) extends AnyVal { + import $this._ + + } + +Here `` results from `` by augmenting any definition in with the parameters and replacing any occurrence of `this` with `$this`. + +For example, the extension + +```scala +extension SeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class SeqOps[T](private val $this: List[T]) extends AnyVal { + import $this._ + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Instance Declarations + +Now, assume an extension + + extension for : { } + +where ``, `` and `` are as before. +This extension is translated to + + implicit class ($this: ) extends { + import $this._ + + } + +As before, `` is computed from `` by replacing any occurrence of `this` with `$this`. However, all parameters in now stay on the class definition, instead of being distributed to all members in ``. This is necessary in general, since `` might contain value definitions or other statements that cannot be +parameterized. + +For example, the extension + +```scala +extension HasEqlImpl[T : Eql) for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +would be translated to + +```scala +implicit class HasEqlForEql[T]($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + import $this._ + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) +} +``` diff --git a/docs/docs/reference/extend/typeclass-traits.md b/docs/docs/reference/extend/typeclass-traits.md new file mode 100644 index 000000000000..1b54ffc6011e --- /dev/null +++ b/docs/docs/reference/extend/typeclass-traits.md @@ -0,0 +1,70 @@ +--- +layout: doc-page +title: "Typeclass Traits" +--- + +Typeclass traits offer two new ways to express the type structure of implementing classes. First, they allow to refer to the implementation type using `This`. Second, they can abstract not only over _instance values_ but also over values that exist only once for each implementing _type_, using "common" definitions. + +A typeclass trait is a trait that extends directly or indirectly the marker trait `scala.TypeClass`. Here are two typeclass traits: +```scala +trait SemiGroup extends TypeClass { + def add(that: This): This +} +trait Monoid extends SemiGroup { + common def unit: This +} +``` +The `SemiGroup` trait refers to the implementation type via `This`. The implementation type can be +an extending class or it can be defined by an instance declaration. Here is an example of the latter: + +```scala +extension IntMonoid for Int : Monoid { + def add(that: Int) = this + that + common def unit = 0 +} +``` +In this extension, the `This` type of `SemiGroup` and `Monoid` is bound to `Int`. + +Here is an example a class extending `Monoid` directly: + +```scala +enum Nat extends Monoid { + case Z + case S(n: Nat) + + def add(that: Nat): Nat = this match { + case Z => that + case S(n) => S(n.add(that)) + } + + common def unit = Z +} +``` + +In this enum, the `This` type of `SemiGroup` and `Monoid` is bound to `Nat`. + +### The `This` type. + +The `This` type can be seen as an analogue of the `this` value. Where the `this` value refers to the "current object" on which some operation is performed, the `This` type refers to the type of that object. Both versions work for implementing classes and for instance declarations. + +Note that the first class extending a typeclass trait fixes the meaning of `This`. For instance, the following is legal: + +```scala +class A extends SemiGroup { + def add(that: A) = ??? +} +class B extends A with Monoid { + common def unit: B = ??? // OK since B <: A = This +} +``` +But we could not have re-implemented `add` in `B` with +```scala + def add(that: B): B = ... +``` +since `This` is already fixed to be `A`. + +`This` can be thought of as am`common` abstract type member of a typeclass trait. It is as if `This` was defined explicitly as +```scala +common type This +``` +While it is possible to constrain or bind `This` explicitly, this is not encouraged because it might conflict with the compiler-generated bindings for `This`. 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 ca456b11af65..57b0a09f36e3 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -29,6 +29,26 @@ sidebar: url: docs/reference/dependent-function-types.html - title: Literal Singleton Types url: docs/reference/singleton-types.html + - title: Extension Clauses + subsection: + - title: Extension Methods + url: docs/reference/extend/extension-methods.html + - title: Instance Declarations + url: docs/reference/extend/instance-declarations.html + - title: Typeclass Traits + url: docs/reference/extend/typeclass-traits.html + - title: Common Declarations + url: docs/reference/extend/common-declarations.html + - title: Context Bounds + uril: docs/reference/extend/context-bounds.html + - title: Parameterized Typeclasses + url: docs/reference/extend/parameterized-typeclasses.html + - title: Factored Instances + url: docs/reference/extend/factored-instances.html + - title: Local Coherence + url: docs/reference/extend/local-coherence.html + - title: Translation + url: docs/reference/extend/translation.html - title: Enums subsection: - title: Enumerations @@ -47,6 +67,8 @@ sidebar: url: docs/reference/inline.html - title: Meta Programming url: docs/reference/principled-meta-programming.html + - title: Opaque Type Aliases + url: docs/reference/opaques.html - title: By-Name Implicits url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala new file mode 100644 index 000000000000..bfb720a6ac28 --- /dev/null +++ b/tests/neg/extensions.scala @@ -0,0 +1,45 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extension CircleOps for Circle { + def circumference = this.radius * math.Pi * 2 + private val p = math.Pi // error: `def` expected + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + abstract class HasAreaClass extends HasArea + + extension Ops2 : HasArea {} // error: `def` expected + extension Ops for Circle extends HasArea {} // error: `def` expected + + extension Circle2 : HasArea { // error: `for` expected + def area = this.radius * this.radius * math.Pi + } + + extension Ops3 for Circle : HasAreaClass { // error: class HasAreaClass is not a trait + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + extension ListOps[T] for List[T] { + type I = Int // error: `def` expected + def second = this.tail.head + } + +// Specific trait implementations + + extension ListOps2 for List[Int] { self => // error: `def` expected + import java.lang._ // error: `def` expected + def maxx = (0 /: this)(_ `max` _) + } +} diff --git a/tests/neg/i2494.scala b/tests/neg/i2494.scala index 21c11242878d..afdd8e92fce5 100644 --- a/tests/neg/i2494.scala +++ b/tests/neg/i2494.scala @@ -1,2 +1,2 @@ -enum +enum // error object // error // error diff --git a/tests/neg/opaque.scala b/tests/neg/opaque.scala new file mode 100644 index 000000000000..3b675f563813 --- /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 + + 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/blueSkyExtensions1.scala b/tests/pending/pos/blueSkyExtensions1.scala new file mode 100644 index 000000000000..56ec9f6e5ac7 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions1.scala @@ -0,0 +1,134 @@ +/** A blue sky sketch how one might evolve extensions to do type classes + * without the current gap between functional and OO patterns. + * Largely inspired by the way Rust does it. + * + * The main additions (to be worked out in detail) is a self type `This` and + * a mechanism that a trait can abstract over companions of classes that implement it. + * Companion types and methods are declared using `static` for now, just to + * pick some familiar notation. + * + * Ideas about `This` (still vague at the moment) + * + * - Treat it as an additional abstract type in a trait, prefixed by the name of the trait + * - An implementing (non-trait) class binds the `This` types of all the traits it implements + * to itself. + * - Later subclasses do not re-bind `This` types already bound by their superclasses. + * (so in that sense, `This`-binding is like trait parameterization, the first implementing + * classes determines the self type and the parameters of a trait) + * - Paramerized classes have parameterized `This` types (e.g. Functor below). + */ +import Predef.{any2stringadd => _, _} +object blueSkyExtensions { + +// Semigroup and Monoid + + trait SemiGroup { + def + (that: This): This + } + + trait Monoid extends SemiGroup { + static def unit: This + } + + extension IntMonoid for Int : Monoid { + static def unit = 0 + def + (that: Int) = this + that + } + + extension StringMonoid for String : Monoid { + static def unit = "" + def + (that: Int) = this ++ that + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + static def pure[A]: This[A] + def map[B](f: A => B): This[B] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.statics[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extension ListMonad[T] for List[T] : Monad[T] { + static def pure[A] = Nil + + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + + extension MonadFlatten[T[X]: Monad[X]] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + static def empty: This[A] + static def apply(xs: A*): This[A] + + def filter(p: A => Boolean): This[A] + } + + trait Iterable[A] extends MonoIterable[A] { + def map[B](f: A => B): This[B] + def flatMap[B](f: A => This[B]): This[B] + } + + extension StringMonoIterable for String : MonoIterable[Char] { + static type This[A] = String + static def empty = "" + static def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + + extension StringIterable for String : Iterable[Char] { + static type This[A] = IndexedSeq[A] + + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extension ListIterable[T] for List[T] : Iterable[T] { + static type This[A] = List[A] + static def empty = Nil + static def apply(xs: A*) = (xs /: Nil)(_ :: _) + + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } +} diff --git a/tests/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-special/typeclass-encoding3.scala b/tests/pos-special/typeclass-encoding3.scala new file mode 100644 index 000000000000..9e4811756f58 --- /dev/null +++ b/tests/pos-special/typeclass-encoding3.scala @@ -0,0 +1,873 @@ +/* + [T : M] = [T] ... (implicit ev: Injectable[T, M]) if M is a normal trait + = [T] ... (implicit ev: M.at[T] & Injectable[T, M]) if M is a trait with common members + = [T] ... (implicit ev: M.at[T]) if M is a typeclass (because they subsume Injectable) + + The context bound [C, M] resolves to: + + - selfInject if C is a class extending a normal trait M + - C.common, C[Ts] if C is a class extending a trait M with common members + - EX if EX is an implicit extension object for C and M + + M.at[T] = Common { type $This = T } + ... + + - Common definitions in traits may not see trait parameters, but common definitions + in classes may see class parameters. + + - A typeclass trait is a trait inheriting directly or indirectly from `scala.TypeClass`. + - A typeclass trait `T` can be only extended by traits and classes that have the same number of type + parameters as `T`, with the same variances. + - The first class implementing a typeclass trait fixes the maning of `This`. +*/ +object runtime { + + trait Injector { + /** The implementating type */ + type $This + + /** The implemented trait */ + type $Instance + + /** The implementation via type `T` for this trait */ + def inject(x: $This): $Instance + } + + trait IdentityInjector extends Injector { + def inject(x: $This): $Instance = x.asInstanceOf + } + + trait SubtypeInjector[T] extends Injector { + type $This = T + type $Instance = T + def inject(x: T): T = x + } + + type Injectable[T, +U] = Injector { type $This = T; type $Instance <: U } + + def selfInject[U, T <: U]: Injectable[T, U] = new SubtypeInjector[T] {} + + trait Common { + type $This <: AnyKind + } + + trait Companion { + /** The `Common` base trait defining common (static) operations of this typeclass */ + type Common <: runtime.Common + + /** Helper type to characterize implementations via type `T` for this typeclass */ + type at[T <: AnyKind] = Common { type $This = T } + + /** The implementation via type `T` for this typeclass, as found by implicit search */ + def at[T <: AnyKind](implicit ev: at[T]): at[T] = ev + } + + trait TypeClass { + /** The companion object of the implementing type */ + val `common`: TypeClass.Common + } + + object TypeClass { + + /** Base trait for companion objects of all implementations of this typeclass */ + trait Common extends runtime.Common with Injector { self => + /** A user-accessible self type */ + type This = $This + + /** The implemented typeclass */ + type $Instance <: TypeClass + + implicit def inject(x: $This): $Instance + } + + /** Base trait for the companion objects of type classes themselves */ + trait Companion extends runtime.Companion { + /** The `Common` base trait defining common (static) operations of this typeclass */ + type Common <: TypeClass.Common + } + } + + implicit def applyInjector[From, U](x: From)(implicit ev: Injector { type $This = From }): ev.$Instance = + ev.inject(x) +} + +/** 0. All combinations of + + - trait / trait with common / extension trait + - extending class / extension + - monomorphic / generic implementation + - context-bound / direct use of type + + trait HasLength { + def length: Int + } + + trait Cmp[A] { + def isSimilar(x: A): Boolean + common def exact: Boolean + } + + trait HasBoundedLength extends HasLength { + common def limit: Int + } + + trait HasBoundedLengthX extends HasBoundedLength with TypeClass { + common def longest: This + } + + class C1(xs: Array[Int]) extends HasLength { + def length = xs.length + } + + class CG1[T](xs: Array[T]) extends HasLength { + def length = xs.length + } + + class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength with Cmp[Seq[Int]] { + def isSimilar(x: Seq[Int]) = xs.deep == x + common def limit = 100 + common def exact = true + } + + class CG2[T](xs: Array[Int]) extends CG1[T](xs) with HasBoundedLength with Cmp[Seq[T]] { + def isSimilar(x: Seq[T]) = xs.deep == x + common def limit = 100 + common def exact = true + } + + final class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { + common def longest = new C3(new Array[Int](limit)) + } + + final class CG3[T](xs: Array[T])(implicit tag: ClassTag[T]) extends CG2[T](xs) with HasBoundedLengthX { + common def longest = new CG3(new Array[T](limit)) + } + + class D1(val xs: Array[Int]) + class DG1[T](val xs: Array[T]) + + class D2(val xs: Array[Int]) + class DG2[T](val xs: Array[T]) + + class D3(val xs: Array[Int]) + class DG3[T](val xs: Array[T]) + + extension DHasLength for D1 : HasLength { + def length = xs.length + } + + extension DGHasLength[T] for DG1[T] : HasLength { + def length = xs.length + } + + extension DHasBoundedLength for D2 : HasBoundedLength { + def length = xs.length + common def limit = 100 + } + + extension DGHasBoundedLength[T] for DG2[T] : HasBoundedLength { + def length = xs.length + common def limit = 100 + } + + extension DHasBoundedLengthX for D3 : HasBoundedLengthX { + def length = xs.length + common def limit = 100 + common def longest = new D3(new Array[Int](limit)) + } + + extension DGHasBoundedLengthX[T](implicit tag: ClassTag[T]) for DG3[T] : HasBoundedLengthX { + def length = xs.length + common def limit = 100 + common def longest = new DG3(new Array[T](limit)) + } + + def length[T : HasLength](x: T) = x.length + + def lengthOK[T : HasBoundedLength](x: T) = + x.length < x.common.limit + + def lengthOKX[T : HasBoundedLengthX](x: T) = + x.length < HasBoundedLengthX.at[T].limit + + def longestLengthOK[T : HasBoundedLengthX](implicit tag: ClassTag[T]) = { + val impl = HasBoundedLengthX.at[T] + impl.longest.length < impl.limit + } + + def length1(x: HasLength) = x.length + def lengthOK1(x: HasBoundedLength) = x.length < x.common.limit + + val xs = Array(1, 2, 3) + val c1 = new C1(xs) + val cg1 = new CG1(xs) + val c2 = new C2(xs) + val cg2 = new CG2(xs) + val c3 = new C3(xs) + val cg3 = new CG3(xs) + + val d1 = new D1(xs) + val dg1 = new DG1(xs) + val d2 = new D2(xs) + val dg2 = new DG2(xs) + val d3 = new D3(xs) + val dg3 = new DG3(xs) + + length(c1) + length(cg1) + length(c2) + length(cg2) + length(c3) + length(cg3) + + length(d1) + length(dg1) + length(d2) + length(dg2) + length(d3) + length(dg3) + + lengthOK(c2) + lengthOK(cg2) + lengthOK(c3) + lengthOK(cg3) + + lengthOK(d2) + lengthOK(dg2) + lengthOK(d3) + lengthOK(dg3) + + lengthOKX(c3) + lengthOKX(cg3) + + lengthOKX(d3) + lengthOKX(dg3) + + longestLengthOK(c3) + longestLengthOK(cg3) + longestLengthOK(d3) + longestLengthOK(cg3) + + length1(c1) + length1(cg1) + length1(c2) + length1(cg2) + length1(c3) + length1(cg3) + + lengthOK1(c2) + lengthOK1(cg2) + lengthOK1(c3) + lengthOK1(cg3) +*/ +object hasLength { + import runtime._ + import reflect.ClassTag + + trait HasLength { + def length: Int + } + + trait Cmp[A] { + val `common`: Cmp.Common + import `common`._ + def isSimilar(x: A): Boolean + } + + object Cmp extends Companion { + trait Common extends runtime.Common { + def exact: Boolean + } + } + + trait HasBoundedLength extends HasLength { + val `common`: HasBoundedLength.Common + import `common`._ + } + + object HasBoundedLength extends Companion { + trait Common extends runtime.Common { + def limit: Int + } + } + + trait HasBoundedLengthX extends HasBoundedLength with TypeClass { + val `common`: HasBoundedLengthX.Common + import `common`._ + } + + object HasBoundedLengthX extends TypeClass.Companion { + trait Common extends HasBoundedLength.Common with TypeClass.Common { self => + type $Instance <: HasBoundedLengthX { val `common`: self.type } + def limit: Int + def longest: This + } + } + + class C1(xs: Array[Int]) extends HasLength { + def length = xs.length + def isSimilar(x: Seq[Int]) = xs.deep == x + } + + class CG1[T](xs: Array[T]) extends HasLength { + def length = xs.length + def isSimilar(x: Seq[T]) = xs.deep == x + } + + class C2(xs: Array[Int]) extends C1(xs) with HasBoundedLength with Cmp[Seq[Int]] { + val `common`: C2.Common = C2.common + import `common`._ + } + + object C2 { + abstract class Common extends HasBoundedLength.Common with Cmp.Common { + def limit = 100 + def exact = true + } + val common = new Common with SubtypeInjector[C2] + def limit = common.limit + def exact = common.exact + } + + class CG2[T](xs: Array[T]) extends CG1(xs) with HasBoundedLength with Cmp[Seq[T]] { + val `common`: CG2.Common[T] = CG2[T] + import `common`._ + } + + object CG2 { + abstract class Common[T] extends HasBoundedLength.Common with Cmp.Common { + def limit = 100 + def exact = true + } + def apply[T] = new Common[T] with SubtypeInjector[CG2[T]] + } + + class C3(xs: Array[Int]) extends C2(xs) with HasBoundedLengthX { + override val `common`: C3.Common = C3.common + import `common`._ + } + + object C3 { + class Common extends C2.Common with HasBoundedLengthX.Common with IdentityInjector { self => + type $This = C3 + type $Instance <: C3 { val `common`: self.type } + def longest = new C3(new Array[Int](limit)) + } + val common = new Common + def limit = common.limit + def exact = common.exact + def longest = common.longest + } + + class CG3[T](xs: Array[T])(implicit tag: ClassTag[T]) extends CG2(xs) with HasBoundedLengthX { + override val `common`: CG3.Common[T] = CG3[T] + import `common`._ + } + + object CG3 { + class Common[T](implicit tag: ClassTag[T]) + extends CG2.Common[T] with HasBoundedLengthX.Common with IdentityInjector { self => + type $This = CG3[T] + type $Instance <: CG3[T] { val `common`: self.type } + def longest = new CG3(new Array[T](limit)) + } + def apply[T](implicit tag: ClassTag[T]) = new Common[T] + } + + class D1(val xs: Array[Int]) + class DG1[T](val xs: Array[T]) + + class D2(val xs: Array[Int]) + class DG2[T](val xs: Array[T]) + + class D3(val xs: Array[Int]) + class DG3[T](val xs: Array[T]) + + implicit object DHasLength extends Injector { + type $This = D1 + type $Instance = HasLength + def inject(x: D1) = new HasLength { + def length = xs.length + } + } + + class DGHasLength[T] extends Injector { + type $This = DG1[T] + type $Instance = HasLength + def inject(x: DG1[T]) = new HasLength { + def length = xs.length + } + } + implicit def DGHasLength[T]: DGHasLength[T] = new DGHasLength + + implicit object DHasBoundedLength extends HasBoundedLength.Common with Injector { self => + type $This = D2 + type $Instance = HasBoundedLength + def inject(x: D2) = new HasBoundedLength { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + } + + class DGHasBoundedLength[T] extends HasBoundedLength.Common with Injector { self => + type $This = DG2[T] + type $Instance = HasBoundedLength + def inject(x: DG2[T]) = new HasBoundedLength { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + } + implicit def DGHasBoundedLength[T]: DGHasBoundedLength[T] = new DGHasBoundedLength + + implicit object DHasBoundedLengthX extends HasBoundedLengthX.Common { self => + type $This = D3 + type $Instance = HasBoundedLengthX { val `common`: self.type } + def inject(x: D3) = new HasBoundedLengthX { + val `common`: self.type = self + import `common`._ + def length = x.length + } + def limit = 100 + def longest = new D3(new Array[Int](limit)) + } + + class DGHasBoundedLengthX[T](implicit tag: ClassTag[T]) extends HasBoundedLengthX.Common { self => + type $This = DG3[T] + type $Instance = HasBoundedLengthX { val `common`: self.type } + def inject(x: DG3[T]) = new HasBoundedLengthX { + val `common`: self.type = self + import `common`._ + def length = xs.length + } + def limit = 100 + def longest = new DG3(new Array[T](limit)) + } + implicit def DGHasBoundedLengthX[T](implicit tag: ClassTag[T]): DGHasBoundedLengthX[T] = new DGHasBoundedLengthX + + def length[T](x: T)(implicit ev: Injectable[T, HasLength]) = x.length + + def lengthOK[T](x: T)(implicit ev: Injectable[T, HasBoundedLength]) = + x.length < x.common.limit + + def lengthOKX[T](x: T)(implicit ev: HasBoundedLength.at[T] & Injectable[T, HasBoundedLength]) = + x.length < HasBoundedLength.at[T].limit + + def longestLengthOK[T](implicit ev: HasBoundedLengthX.at[T], tag: ClassTag[T]) = { + val impl = HasBoundedLengthX.at[T] + impl.longest.length < impl.limit + } + + def length1(x: HasLength) = x.length + def lengthOK1(x: HasBoundedLength) = x.length < x.common.limit + + def ctag[T](implicit tag: ClassTag[T]) = tag + + val xs = Array(1, 2, 3) + val intTag = implicitly[ClassTag[Int]] + + val c1 = new C1(xs) + val cg1 = new CG1(xs) + val c2 = new C2(xs) + val cg2 = new CG2(xs) + val c3 = new C3(xs) + val cg3 = new CG3(xs) + + val d1 = new D1(xs) + val dg1 = new DG1(xs) + val d2 = new D2(xs) + val dg2 = new DG2(xs) + val d3 = new D3(xs) + val dg3 = new DG3(xs) + + length(c1)(selfInject) + length(cg1)(selfInject) + length(c2)(C2.common) + length(cg2)(CG2[Int]) + length(c3)(C3.common) + length(cg3)(CG3[Int]) + + length(d1)(DHasLength) + length(dg1)(DGHasLength[Int]) + length(d2)(DHasBoundedLength) + length(dg2)(DGHasBoundedLength[Int]) + length(d3)(DHasBoundedLengthX) + length(dg3)(DGHasBoundedLengthX[Int]) + + length(d1) + length(dg1) + length(d2) + length(dg2) + length(d3) + length(dg3) + + lengthOK(c2)(C2.common) + lengthOK(cg2)(CG2[Int]) + lengthOK(c3)(C3.common) + lengthOK(cg3)(CG3[Int]) + + lengthOK(d2)(DHasBoundedLength) + lengthOK(dg2)(DGHasBoundedLength[Int]) + lengthOK(d3)(DHasBoundedLengthX) + lengthOK(dg3)(DGHasBoundedLengthX[Int]) + + lengthOK(d2) + lengthOK(dg2) + lengthOK(d3) + lengthOK(dg3) + + lengthOKX(c3)(C3.common) + lengthOKX(cg3)(CG3[Int]) + + lengthOKX(d3)(DHasBoundedLengthX) + lengthOKX(dg3)(DGHasBoundedLengthX[Int]) + + lengthOKX(d3) + lengthOKX(dg3) + + longestLengthOK[C3](C3.common, ctag[C3]) + longestLengthOK[CG3[Int]](CG3[Int], ctag[CG3[Int]]) + longestLengthOK[D3] + longestLengthOK[DG3[Int]] + + length1(c1) + length1(cg1) + length1(c2) + length1(cg2) + length1(c3) + length1(cg3) + + lengthOK1(c2) + lengthOK1(cg2) + lengthOK1(c3) + lengthOK1(cg3) +} +/** 1. Simple type classes with monomorphic implementations and direct extensions. + + trait SemiGroup extends TypeClass { + def add(that: This): This + def add2(that: This): This = add(that).add(that) + } + + trait Monoid extends SemiGroup { + common def unit: This + } + + extension IntSemiGroup for Int : SemiGroup { + def add(that: Int) = this + that + } + + extension IntMonoid for Int : Monoid { + common def unit = 0 + } + + extension StringOps for String : Monoid { + def add(that: Int) = this ++ that + common def unit = "" + } + + enum Nat extends Monoid { + case Z + case S(n: Nat) + + def add(that: Nat): Nat = this match { + case Z => that + case S(n) => S(n.add(that)) + } + } + common { + def unit = Z + } + + def sum[T: Monoid](xs: List[T]): T = + (Monoid.at[T].unit /: xs)(_ `add` _) +*/ + +import runtime._ + +object semiGroups { + + trait SemiGroup extends TypeClass { + val `common`: SemiGroup.Common + import `common`._ + def add(that: This): This + def add2(that: This): This = add(that).add(that) + } + + object SemiGroup extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type $Instance <: SemiGroup { val `common`: self.type } + } + } + + trait Monoid extends SemiGroup { + val `common`: Monoid.Common + import `common`._ + } + object Monoid extends TypeClass.Companion { + trait Common extends SemiGroup.Common { self => + type $Instance <: Monoid { val `common`: self.type } + def unit: This + } + } + + implicit object IntSemiGroup extends SemiGroup.Common { self => + type $This = Int + type $Instance = SemiGroup { val `common`: self.type } + implicit def inject($this: Int) = new SemiGroup { + val `common`: self.type = self + def add(that: Int): Int = $this + that + } + } + + implicit object IntMonoid extends Monoid.Common { self => + type $This = Int + type $Instance = Monoid { val `common`: self.type } + def unit: Int = 0 + implicit def inject($this: Int) = new Monoid { + val `common`: self.type = self + def add(that: This): This = $this.add(that) + } + } + + implicit object StringMonoid extends Monoid.Common { self => + type $This = String + type $Instance = Monoid { val `common`: self.type } + def unit = "" + def inject($this: String) = new Monoid { + val `common`: self.type = self + def add(that: This): This = $this.concat(that) + } + } + + enum Nat extends Monoid { + case Z + case S(n: Nat) + + val `common`: Nat.Common = Nat.common + import `common`._ + + def add(that: Nat): Nat = this match { + case Z => that + case S(n) => S(n.add(that)) + } + } + object Nat { + class Common extends Monoid.Common with IdentityInjector { self => + type $This = Nat + type $Instance <: Nat { val `common`: self.type } + def unit = Nat.Z + } + val common = new Common + def unit = common.unit + } + import Nat.{Z, S} + + def sum[T](xs: List[T])(implicit $ev: Monoid.at[T]) = + (Monoid.at[T].unit /: xs)((x, y) => x `add` y) + + sum(List(1, 2, 3)) + sum(List("hello ", "world!")) + sum(List(Z, S(Z), S(S(Z))))(Nat.common) +} + +/** 2. Generic implementations of simple type classes. + + trait Ord extends TypeClass { + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + + common def minimum: This + } + + extension IntOrd for Int : Ord { + def compareTo(that: Int) = + if (this < that) -1 else if (this > that) +1 else 0 + + common def minimum = Int.MinValue + } + + extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + + common def minimum: List[T] = Nil + } + + def min[T: Ord](x: T, y: T) = if (x < y) x else y + + def inf[T: Ord](xs: List[T]): T = (Ord.at[T].minimum /: xs)(min) +*/ +object ord { + + trait Ord extends TypeClass { + val `common`: Ord.Common + import `common`._ + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + } + object Ord extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type $Instance <: Ord { val `common`: self.type } + def minimum: This + } + } + + implicit object IntOrd extends Ord.Common { + type $This = Int + type $Instance = Ord { val `common`: IntOrd.type } + val minimum: Int = Int.MinValue + def inject($this: Int) = new Ord { + val `common`: IntOrd.this.type = IntOrd.this + import `common`._ + def compareTo(that: This): Int = + if (this < that) -1 else if (this > that) +1 else 0 + } + } + + class ListOrd[T](implicit $ev: Ord.at[T]) extends Ord.Common { self => + type $This = List[T] + type $Instance = Ord { val `common`: self.type } + def minimum: List[T] = Nil + def inject($this: List[T]) = new Ord { + val `common`: self.type = self + import `common`._ + def compareTo(that: List[T]): Int = ($this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + } + implicit def ListOrd[T](implicit $ev: Ord.at[T]): ListOrd[T] = + new ListOrd[T] + + def min[T](x: T, y: T)(implicit $ev: Ord.at[T]): T = + if (x < y) x else y + + def inf[T](xs: List[T])(implicit $ev: Ord.at[T]): T = { + val smallest = Ord.at[T].minimum + (smallest /: xs)(min) + } + + inf(List[Int]()) + inf(List(List(1, 2), List(1, 2, 3))) + inf(List(List(List(1), List(2)), List(List(1), List(2), List(3)))) +} + +/** 3. Higher-kinded type classes + + trait Functor[A] extends TypeClass { + def map[B](f: A => B): This[B] + + common def pure[A](x: A): This[A] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[_] : Functor](n: Int, f: A => A): F[A] = + if (n == 0) Functor.at[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extension ListMonad[T] for List[T] : Monad[T] { + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + common def pure[A] = Nil + } + + extension MonadFlatten[T[_]: Monad] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) + } +*/ +object runtime1 { + + trait TypeClass1[X] { + val `common`: TypeClass1.Common + } + object TypeClass1 { + trait Common extends runtime.Common { + type $This[X] + type This = $This + type $Instance[X] <: TypeClass1[X] + implicit def inject[A](x: This[A]): $Instance[A] + } + + trait Companion extends runtime.Companion { + type Common <: TypeClass1.Common + } + } + + implicit def applyInjector1[A, From[_]](x: From[A]) + (implicit ev: TypeClass1.Common { type This = From }): ev.$Instance[A] = + ev.inject(x) +} +import runtime1._ + +object functors { + + trait Functor[A] extends TypeClass1[A] { + val `common`: Functor.Common + import `common`._ + def map[B](f: A => B): This[B] + } + object Functor extends TypeClass1.Companion { + trait Common extends TypeClass1.Common { self => + type $Instance[X] <: Functor[X] { val `common`: self.type } + def pure[A](x: A): This[A] + } + } + + trait Monad[A] extends Functor[A] { + val `common`: Monad.Common + import `common`._ + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(`common`.pure)) + } + object Monad extends TypeClass1.Companion { + trait Common extends Functor.Common { self => + type $Instance[X] <: Monad[X] { val `common`: self.type } + } + } + + def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.at[F]): F[A] = + if (n == 0) Functor.at[F].pure(x) + else develop(n - 1, x, f).map(f) + + implicit object ListMonad extends Monad.Common { + type $This = List + type $Instance[X] = Monad[X] { val `common`: ListMonad.type } + def pure[A](x: A) = x :: Nil + def inject[A]($this: List[A]) = new Monad[A] { + val `common`: ListMonad.this.type = ListMonad + import `common`._ + def flatMap[B](f: A => List[B]): List[B] = $this.flatMap(f) + } + } + + object MonadFlatten { + def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.at[T]): T[A] = + $this.flatMap(identity ) + } + + MonadFlatten.flattened(List(List(1, 2, 3), List(4, 5))) +} diff --git a/tests/pos/commons.scala b/tests/pos/commons.scala new file mode 100644 index 000000000000..0c0cab283d03 --- /dev/null +++ b/tests/pos/commons.scala @@ -0,0 +1,68 @@ +/** Simple common definitions, no This type */ +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Text): Text + def toStr: String + def flatten = `common`.fromString(toStr) +} +object Text { + trait Common { self => + def fromString(str: String): Text + def fromStrings(strs: String*): Text = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val common: FlatText.type = FlatText + import `common`._ + + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: Text) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val common: ConcText.type = ConcText + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: Text): Text = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + def fromString(str: String) = Str(str) +} + +object Test extends App { + val txt1 = FlatText.fromStrings("hel", "lo") + val txt2 = ConcText.fromString("world") + println(txt2.concat(txt1)) + assert(txt1.concat(txt2).toStr == "helloworld") + assert(txt2.concat(txt1).toStr == "worldhello") + assert(txt1.concat(txt2)(5) == 'w') +} \ No newline at end of file diff --git a/tests/pos/commons1.scala b/tests/pos/commons1.scala new file mode 100644 index 000000000000..94efd9177fb0 --- /dev/null +++ b/tests/pos/commons1.scala @@ -0,0 +1,69 @@ +/** Simple common definitions, with This type */ +trait Text { + val `common`: Text.Common + import `common`._ + + def length: Int + def apply(idx: Int): Char + def concat(txt: Instance): Instance + def toStr: String + def flatten = common.fromString(toStr) +} +object Text { + trait Common { self => + type Instance <: Text { val `common`: self.type } + def fromString(str: String): Instance + def fromStrings(strs: String*): Instance = + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) + } +} + +class FlatText(str: String) extends Text { + val common: FlatText.type = FlatText + def length = str.length + def apply(n: Int) = str.charAt(n) + def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) + def toStr = str +} +object FlatText extends Text.Common { + type Instance = FlatText + def fromString(str: String) = new FlatText(str) +} + +enum ConcText extends Text { + val common: ConcText.type = ConcText + + case Str(s: String) + case Conc(t1: Text, t2: Text) + + lazy val length = this match { + case Str(s) => s.length + case Conc(t1, t2) => t1.length + t2.length + } + + def apply(n: Int) = this match { + case Str(s) => s.charAt(n) + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) + } + + def concat(txt: ConcText): ConcText = Conc(this, txt) + + def toStr: String = this match { + case Str(s) => s + case Conc(t1, t2) => t1.toStr ++ t2.toStr + } +} + +object ConcText extends Text.Common { + type Instance = ConcText + def fromString(str: String) = Str(str) +} + +object Test extends App { + val txt1 = ConcText.fromStrings("hel", "lo") + val txt2 = ConcText.fromString("world") + println(txt2.concat(txt1)) + assert(txt1.concat(txt2).toStr == "helloworld") + assert(txt2.concat(txt1).toStr == "worldhello") + assert(txt1.concat(txt2)(5) == 'w') +} \ No newline at end of file diff --git a/tests/pos/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-extension.scala b/tests/pos/opaque-extension.scala new file mode 100644 index 000000000000..69230356b0dc --- /dev/null +++ b/tests/pos/opaque-extension.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + extension LogarithmOps for Logarithm { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(this) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(this) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(this + that) + } + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l * l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = l3.toDouble + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/pos/opaque-goups.scala b/tests/pos/opaque-goups.scala new file mode 100644 index 000000000000..4391d756494b --- /dev/null +++ b/tests/pos/opaque-goups.scala @@ -0,0 +1,95 @@ +package object groups { + trait Semigroup[A] { + def combine(x: A, y: A): A + } + + object Semigroup { + def instance[A](f: (A, A) => A): Semigroup[A] = + new Semigroup[A] { + def combine(x: A, y: A): A = f(x, y) + } + } + + type Id[A] = A + + trait Wrapping[F[_]] { + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](ga: G[F[A]]): G[A] + } + + abstract class Wrapper[F[_]] { self => + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](gfa: G[F[A]]): G[A] + + final def apply[A](a: A): F[A] = wraps[Id, A](a) + + implicit object WrapperWrapping extends Wrapping[F] { + def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga) + def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga) + } + } + + opaque type First[A] = A + object First extends Wrapper[First] { + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga + def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa + implicit def firstSemigroup[A]: Semigroup[First[A]] = + Semigroup.instance((x, y) => x) + } + + opaque type Last[A] = A + object Last extends Wrapper[Last] { + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } + + opaque type Min[A] = A + object Min extends Wrapper[Min] { + def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga + def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa + implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] = + Semigroup.instance(o.min) + } + + opaque type Max[A] = A + object Max extends Wrapper[Max] { + def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga + def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa + implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] = + Semigroup.instance(o.max) + } + + opaque type Plus[A] = A + object Plus extends Wrapper[Plus] { + def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga + def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa + implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] = + Semigroup.instance(n.plus) + } + + opaque type Times[A] = A + object Times extends Wrapper[Times] { + def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga + def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa + implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] = + Semigroup.instance(n.times) + } + + opaque type Reversed[A] = A + object Reversed extends Wrapper[Reversed] { + def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga + def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa + implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] = + o.reverse + } + + opaque type Unordered[A] = A + object Unordered extends Wrapper[Unordered] { + def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga + def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa + implicit def unorderedOrdering[A]: Ordering[Unordered[A]] = + Ordering.by(_ => ()) + } +} \ No newline at end of file diff --git a/tests/pos/opaque-groups.scala b/tests/pos/opaque-groups.scala new file mode 100644 index 000000000000..4391d756494b --- /dev/null +++ b/tests/pos/opaque-groups.scala @@ -0,0 +1,95 @@ +package object groups { + trait Semigroup[A] { + def combine(x: A, y: A): A + } + + object Semigroup { + def instance[A](f: (A, A) => A): Semigroup[A] = + new Semigroup[A] { + def combine(x: A, y: A): A = f(x, y) + } + } + + type Id[A] = A + + trait Wrapping[F[_]] { + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](ga: G[F[A]]): G[A] + } + + abstract class Wrapper[F[_]] { self => + def wraps[G[_], A](ga: G[A]): G[F[A]] + def unwrap[G[_], A](gfa: G[F[A]]): G[A] + + final def apply[A](a: A): F[A] = wraps[Id, A](a) + + implicit object WrapperWrapping extends Wrapping[F] { + def wraps[G[_], A](ga: G[A]): G[F[A]] = self.wraps(ga) + def unwrap[G[_], A](ga: G[F[A]]): G[A] = self.unwrap(ga) + } + } + + opaque type First[A] = A + object First extends Wrapper[First] { + def wraps[G[_], A](ga: G[A]): G[First[A]] = ga + def unwrap[G[_], A](gfa: G[First[A]]): G[A] = gfa + implicit def firstSemigroup[A]: Semigroup[First[A]] = + Semigroup.instance((x, y) => x) + } + + opaque type Last[A] = A + object Last extends Wrapper[Last] { + def wraps[G[_], A](ga: G[A]): G[Last[A]] = ga + def unwrap[G[_], A](gfa: G[Last[A]]): G[A] = gfa + implicit def lastSemigroup[A]: Semigroup[Last[A]] = + Semigroup.instance((x, y) => y) + } + + opaque type Min[A] = A + object Min extends Wrapper[Min] { + def wraps[G[_], A](ga: G[A]): G[Min[A]] = ga + def unwrap[G[_], A](gfa: G[Min[A]]): G[A] = gfa + implicit def minSemigroup[A](implicit o: Ordering[A]): Semigroup[Min[A]] = + Semigroup.instance(o.min) + } + + opaque type Max[A] = A + object Max extends Wrapper[Max] { + def wraps[G[_], A](ga: G[A]): G[Max[A]] = ga + def unwrap[G[_], A](gfa: G[Max[A]]): G[A] = gfa + implicit def maxSemigroup[A](implicit o: Ordering[A]): Semigroup[Max[A]] = + Semigroup.instance(o.max) + } + + opaque type Plus[A] = A + object Plus extends Wrapper[Plus] { + def wraps[G[_], A](ga: G[A]): G[Plus[A]] = ga + def unwrap[G[_], A](gfa: G[Plus[A]]): G[A] = gfa + implicit def plusSemigroup[A](implicit n: Numeric[A]): Semigroup[Plus[A]] = + Semigroup.instance(n.plus) + } + + opaque type Times[A] = A + object Times extends Wrapper[Times] { + def wraps[G[_], A](ga: G[A]): G[Times[A]] = ga + def unwrap[G[_], A](gfa: G[Times[A]]): G[A] = gfa + implicit def timesSemigroup[A](implicit n: Numeric[A]): Semigroup[Times[A]] = + Semigroup.instance(n.times) + } + + opaque type Reversed[A] = A + object Reversed extends Wrapper[Reversed] { + def wraps[G[_], A](ga: G[A]): G[Reversed[A]] = ga + def unwrap[G[_], A](gfa: G[Reversed[A]]): G[A] = gfa + implicit def reversedOrdering[A](implicit o: Ordering[A]): Ordering[Reversed[A]] = + o.reverse + } + + opaque type Unordered[A] = A + object Unordered extends Wrapper[Unordered] { + def wraps[G[_], A](ga: G[A]): G[Unordered[A]] = ga + def unwrap[G[_], A](gfa: G[Unordered[A]]): G[A] = gfa + implicit def unorderedOrdering[A]: Ordering[Unordered[A]] = + Ordering.by(_ => ()) + } +} \ No newline at end of file diff --git a/tests/pos/opaque-immutable-array.scala b/tests/pos/opaque-immutable-array.scala new file mode 100644 index 000000000000..ad657ff17a10 --- /dev/null +++ b/tests/pos/opaque-immutable-array.scala @@ -0,0 +1,43 @@ +object ia { + + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + object IArray { + @inline final def initialize[A](body: => Array[A]): IArray[A] = body + + @inline final def size[A](ia: IArray[A]): Int = ia.length + @inline final def get[A](ia: IArray[A], i: Int): A = ia(i) + + // return a sorted copy of the array + def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = { + val arr = Arrays.copyOf(ia, ia.length) + scala.util.Sorting.quickSort(arr) + arr + } + + // use a standard java method to search a sorted IArray. + // (note that this doesn't mutate the array). + def binarySearch(ia: IArray[Long], elem: Long): Int = + Arrays.binarySearch(ia, elem) + } + + // same as IArray.binarySearch but implemented by-hand. + // + // given a sorted IArray, returns index of `elem`, + // or a negative value if not found. + def binaryIndexOf(ia: IArray[Long], elem: Long): Int = { + var lower: Int = 0 + var upper: Int = IArray.size(ia) + while (lower <= upper) { + val middle = (lower + upper) >>> 1 + val n = IArray.get(ia, middle) + + if (n == elem) return middle + else if (n < elem) lower = middle + 1 + else upper = middle - 1 + } + -lower - 1 + } +} \ No newline at end of file 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..036f75b1cc05 --- /dev/null +++ b/tests/pos/opaque.scala @@ -0,0 +1,36 @@ +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 = Logarithm(`this` + that) + } + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l * l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = l3.toDouble + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/pos/t8306.scala b/tests/pos/t8306.scala index e04b054eb9bb..f6b930bada74 100644 --- a/tests/pos/t8306.scala +++ b/tests/pos/t8306.scala @@ -1,6 +1,6 @@ class Si8306 { def foo: Int = 123 - lazy val extension: Int = + lazy val ext: Int = foo match { case idx if idx != -1 => 15 case _ => 17 diff --git a/tests/pos/tagging.scala b/tests/pos/tagging.scala new file mode 100644 index 000000000000..3a9caa2129f9 --- /dev/null +++ b/tests/pos/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) // 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) +} diff --git a/tests/pos/typeclass-encoding2.scala b/tests/pos/typeclass-encoding2.scala index 3269430435de..3ddf1ef824f1 100644 --- a/tests/pos/typeclass-encoding2.scala +++ b/tests/pos/typeclass-encoding2.scala @@ -2,25 +2,21 @@ trait SemiGroup extends TypeClass { def add(that: This): This + def add2(that: This): This = add(that).add(that) } - trait Monoid extends SemiGroup - common { - def unit: This + trait Monoid extends SemiGroup { + common def unit: This } extension IntOps for Int : Monoid { def add(that: Int) = this + that - } - common { - def unit = 0 + common def unit = 0 } extension StringOps for String : Monoid { def add(that: Int) = this ++ that - } - common { - def unit = "" + common def unit = "" } enum Nat extends Monoid { @@ -42,69 +38,87 @@ object runtime { trait TypeClass { - val commons: TypeClassCommon - type This = commons.This + /** The companion object of the implementing type */ + val `common`: TypeClass.Common } - trait TypeClassCommon { self => - type This - type Instance <: TypeClass - def inject(x: This): Instance { val commons: self.type } - } + object TypeClass { + + /** Base trait for companion objects of all implementations of this typeclass */ + trait Common { self => + + /** The implementating type */ + type This + + /** The implemented typeclass */ + type Instance <: TypeClass { val `common`: self.type } + + /** Create instances of typeclass from instances of the implementing type */ + implicit def decorate(x: This): Instance + } - trait TypeClassCompanion { - type Impl[T] <: TypeClassCommon { type This = T } - def impl[T](implicit ev: Impl[T]): Impl[T] = ev + /** Base trait for the companion objects of type classes themselves */ + trait Companion { + + /** The `Common` base trait defining common (static) operations of this typeclass */ + type Common <: TypeClass.Common + + /** Helper type to characterize implementations via type `T` for this typeclass */ + type Impl[T] = Common { type This = T } + + /** The implementation via type `T` for this typeclass, as found by implicit search */ + def impl[T](implicit ev: Impl[T]): Impl[T] = ev + } } - implicit def inject[From](x: From) - (implicit ev: TypeClassCommon { type This = From }): ev.Instance { type This = From } = - ev.inject(x) + implicit def decorateTypeClass[From](x: From) + (implicit ev: TypeClass.Common { type This = From }): ev.Instance = + ev.decorate(x) } import runtime._ object semiGroups { trait SemiGroup extends TypeClass { - val commons: SemiGroupCommon - import commons._ + val `common`: SemiGroup.Common + import `common`._ def add(that: This): This + def add2(that: This): This = add(that).add(that) } - trait SemiGroupCommon extends TypeClassCommon { - type Instance <: SemiGroup - } - object SemiGroup extends TypeClassCompanion { - type Impl[T] = SemiGroupCommon { type This = T } + + object SemiGroup extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type Instance <: SemiGroup { val `common`: self.type } + } } trait Monoid extends SemiGroup { - val commons: MonoidCommon - import commons._ - } - trait MonoidCommon extends SemiGroupCommon { - type Instance <: Monoid - def unit: This + val `common`: Monoid.Common + import `common`._ } - object Monoid extends TypeClassCompanion { - type Impl[T] = MonoidCommon { type This = T } + object Monoid extends TypeClass.Companion { + trait Common extends SemiGroup.Common { self => + type Instance <: Monoid { val `common`: self.type } + def unit: This + } } - implicit object IntOps extends MonoidCommon { + implicit object IntOps extends Monoid.Common { type This = Int - type Instance = Monoid + type Instance = Monoid { val `common`: IntOps.type } def unit: Int = 0 - def inject($this: Int) = new Monoid { - val commons: IntOps.this.type = IntOps.this + def decorate($this: Int) = new Monoid { + val `common`: IntOps.this.type = IntOps.this def add(that: This): This = $this + that } } - implicit object StringOps extends MonoidCommon { + implicit object StringOps extends Monoid.Common { type This = String - type Instance = Monoid + type Instance = Monoid { val `common`: StringOps.type } def unit = "" - def inject($this: String) = new Monoid { - val commons: StringOps.this.type = StringOps.this + def decorate($this: String) = new Monoid { + val `common`: StringOps.this.type = StringOps.this def add(that: This): This = $this.concat(that) } } @@ -118,19 +132,19 @@ object semiGroups { case S(n) => S(n.add(that)) } - val commons: Nat.type = Nat + val `common`: Nat.type = Nat } - object Nat extends MonoidCommon { + object Nat extends Monoid.Common { type This = Nat type Instance = Nat def unit = Nat.Z - def inject($this: Nat) = $this + def decorate($this: Nat) = $this } import Nat.{Z, S} implicit def NatOps: Nat.type = Nat - def sum[T](xs: List[T])(implicit ev: Monoid.Impl[T]) = + def sum[T](xs: List[T])(implicit $ev: Monoid.Impl[T]) = (Monoid.impl[T].unit /: xs)((x, y) => x `add` y) sum(List(1, 2, 3)) @@ -144,17 +158,15 @@ object semiGroups { def compareTo(that: This): Int def < (that: This) = compareTo(that) < 0 def > (that: This) = compareTo(that) > 0 - } - common { - val minimum: This + + common val minimum: This } extension IntOrd for Int : Ord { def compareTo(that: Int) = if (this < that) -1 else if (this > that) +1 else 0 - } - common { - val minimum = Int.MinValue + + common val minimum = Int.MinValue } extension ListOrd[T : Ord] for List[T] : Ord { @@ -166,9 +178,8 @@ object semiGroups { val fst = x.compareTo(y) if (fst != 0) fst else xs.compareTo(ys) } - } - common { - val minimum = Nil + + common val minimum = Nil } def min[T: Ord](x: T, y: T) = if (x < y) x else y @@ -178,39 +189,38 @@ object semiGroups { object ord { trait Ord extends TypeClass { - val commons: OrdCommon - import commons._ + val `common`: Ord.Common + import `common`._ def compareTo(that: This): Int def < (that: This) = compareTo(that) < 0 def > (that: This) = compareTo(that) > 0 } - trait OrdCommon extends TypeClassCommon { - type Instance <: Ord - def minimum: This - } - object Ord extends TypeClassCompanion { - type Impl[T] = OrdCommon { type This = T } + object Ord extends TypeClass.Companion { + trait Common extends TypeClass.Common { self => + type Instance <: Ord { val `common`: self.type } + def minimum: This + } } - implicit object IntOrd extends OrdCommon { + implicit object IntOrd extends Ord.Common { type This = Int - type Instance = Ord + type Instance = Ord { val `common`: IntOrd.type } val minimum: Int = Int.MinValue - def inject($this: Int) = new Ord { - val commons: IntOrd.this.type = IntOrd.this - import commons._ + def decorate($this: Int) = new Ord { + val `common`: IntOrd.this.type = IntOrd.this + import `common`._ def compareTo(that: This): Int = if (this < that) -1 else if (this > that) +1 else 0 } } - class ListOrd[T](implicit ev: Ord.Impl[T]) extends OrdCommon { self => + class ListOrd[T](implicit $ev: Ord.Impl[T]) extends Ord.Common { self => type This = List[T] - type Instance = Ord + type Instance = Ord { val `common`: self.type } def minimum: List[T] = Nil - def inject($this: List[T]) = new Ord { - val commons: self.type = self - import commons._ + def decorate($this: List[T]) = new Ord { + val `common`: self.type = self + import `common`._ def compareTo(that: List[T]): Int = ($this, that) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -222,13 +232,13 @@ object ord { } } - implicit def listOrd[T](implicit ev: Ord.Impl[T]): ListOrd[T] = + implicit def listOrd[T](implicit $ev: Ord.Impl[T]): ListOrd[T] = new ListOrd[T] - def min[T](x: T, y: T)(implicit ev: Ord.Impl[T]): T = + def min[T](x: T, y: T)(implicit $ev: Ord.Impl[T]): T = if (x < y) x else y - def inf[T](xs: List[T])(implicit ev: Ord.Impl[T]): T = { + def inf[T](xs: List[T])(implicit $ev: Ord.Impl[T]): T = { val smallest = Ord.impl[T].minimum (smallest /: xs)(min) } @@ -242,9 +252,8 @@ object ord { trait Functor[A] extends TypeClass1 { def map[B](f: A => B): This[B] - } - common { - def pure[A](x: A): This[A] + + common def pure[A](x: A): This[A] } // Generically, `pure[A]{.map(f)}^n` @@ -258,12 +267,11 @@ object ord { } extension ListMonad[T] for List[T] : Monad[T] { - static def pure[A] = Nil - def flatMap[B](f: A => List[B]): List[B] = this match { case x :: xs => f(x) ++ xs.flatMap(f) case Nil => Nil } + common def pure[A] = Nil } extension MonadFlatten[T[X]: Monad[X]] for T[T[A]] { @@ -272,75 +280,74 @@ object ord { */ object runtime1 { - trait TypeClass1 { - val commons: TypeClassCommon1 - type This = commons.This - } - - trait TypeClassCommon1 { self => - type This[X] - type Instance[X] <: TypeClass1 - def inject[A](x: This[A]): Instance[A] { val commons: self.type } + trait TypeClass1[X] { + val `common`: TypeClass1.Common } + object TypeClass1 { + trait Common { self => + type This[X] + type Instance[X] <: TypeClass1[X] { val `common`: self.type } + def decorate[A](x: This[A]): Instance[A] + } - trait TypeClassCompanion1 { - type Impl[T[_]] <: TypeClassCommon1 { type This = T } - def impl[T[_]](implicit ev: Impl[T]): Impl[T] = ev + trait Companion { + type Common <: TypeClass1.Common + type Impl[T[_]] = Common { type This = T } + def impl[T[_]](implicit ev: Impl[T]): Impl[T] = ev + } } - implicit def inject1[A, From[_]](x: From[A]) - (implicit ev: TypeClassCommon1 { type This = From }): ev.Instance[A] { type This = From } = - ev.inject(x) + implicit def decorateTypeClass1[A, From[_]](x: From[A]) + (implicit ev: TypeClass1.Common { type This = From }): ev.Instance[A] = + ev.decorate(x) } import runtime1._ object functors { - trait Functor[A] extends TypeClass1 { - val commons: FunctorCommon - import commons._ + trait Functor[A] extends TypeClass1[A] { + val `common`: Functor.Common + import `common`._ def map[B](f: A => B): This[B] } - trait FunctorCommon extends TypeClassCommon1 { - type Instance[X] <: Functor[X] - def pure[A](x: A): This[A] - } - object Functor extends TypeClassCompanion1 { - type Impl[T[_]] = FunctorCommon { type This = T } + object Functor extends TypeClass1.Companion { + trait Common extends TypeClass1.Common { self => + type Instance[X] <: Functor[X] { val `common`: self.type } + def pure[A](x: A): This[A] + } } trait Monad[A] extends Functor[A] { - val commons: MonadCommon - import commons._ + val `common`: Monad.Common + import `common`._ def flatMap[B](f: A => This[B]): This[B] - def map[B](f: A => B) = this.flatMap(f.andThen(commons.pure)) + def map[B](f: A => B) = this.flatMap(f.andThen(`common`.pure)) } - trait MonadCommon extends FunctorCommon { - type Instance[X] <: Monad[X] - } - object Monad extends TypeClassCompanion1 { - type Impl[T[_]] = MonadCommon { type This = T } + object Monad extends TypeClass1.Companion { + trait Common extends Functor.Common { self => + type Instance[X] <: Monad[X] { val `common`: self.type } + } } - def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit ev: Functor.Impl[F]): F[A] = + def develop[A, F[X]](n: Int, x: A, f: A => A)(implicit $ev: Functor.Impl[F]): F[A] = if (n == 0) Functor.impl[F].pure(x) else develop(n - 1, x, f).map(f) - implicit object ListMonad extends MonadCommon { + implicit object ListMonad extends Monad.Common { type This = List - type Instance = Monad + type Instance[X] = Monad[X] { val `common`: ListMonad.type } def pure[A](x: A) = x :: Nil - def inject[A]($this: List[A]) = new Monad[A] { - val commons: ListMonad.this.type = ListMonad - import commons._ + def decorate[A]($this: List[A]) = new Monad[A] { + val `common`: ListMonad.this.type = ListMonad + import `common`._ def flatMap[B](f: A => List[B]): List[B] = $this.flatMap(f) } } object MonadFlatten { - def flattened[T[_], A]($this: T[T[A]])(implicit ev: Monad.Impl[T]): T[A] = + def flattened[T[_], A]($this: T[T[A]])(implicit $ev: Monad.Impl[T]): T[A] = $this.flatMap(identity ) } MonadFlatten.flattened(List(List(1, 2, 3), List(4, 5))) -} \ No newline at end of file +} diff --git a/tests/run/extensions.check b/tests/run/extensions.check new file mode 100644 index 000000000000..748a6ca11e55 --- /dev/null +++ b/tests/run/extensions.check @@ -0,0 +1,16 @@ +12.566370614359172 +(1,a) +true +false +true +false +true +false +true +false +2 +List(1, 2, 3) +3 +3 +false +true diff --git a/tests/run/extensions.scala b/tests/run/extensions.scala new file mode 100644 index 000000000000..1ed7ff89bda5 --- /dev/null +++ b/tests/run/extensions.scala @@ -0,0 +1,166 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extension CircleOps for Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + extension CircleHasArea for Circle : HasArea { + def area = radius * this.radius * math.Pi + } + +// Generic extendations + + extension ListOps[T] for List[T] { + def second = tail.head + } + +// Specific extendations + + extension IntListOps for List[Int] { + def maxx = (0 /: this)(_ `max` _) + } + + extension IntArrayOps for Array[Int] { + def maxx = (0 /: this)(_ `max` _) + } + +// Conditional extension methods + + case class Rectangle[T](x: T, y: T, width: T, height: T) + + trait Eql[T] { + def eql (x: T, y: T): Boolean + } + + extension RectangleOps[T: Eql] for Rectangle[T] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + + extension RectangleOps2[T](implicit ev: Eql[T]) for Rectangle[T] { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(width, height) + } + +// Simple generic extensions + + extension Pairer[T] for T { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic extensions + + trait HasEql[T] { + def === (that: T): Boolean + } + + extension HasEqlDeco[T : Eql] for T : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + extension InfixEql[T](implicit ev: Eql[T]) for T { + def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + extension RectangleHasEql[T : Eql] for Rectangle[T] : HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } +} + +object extensions2 { + import extensions.{Eql, HasEqlDeco} + // Nested generic arguments + + extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = (this :\ (Nil: List[T]))(_ ++ _) + } + + extension EqlDeco[T : Eql] for (T, T) { + def isSame = this._1 === this._2 + def isSame2 = HasEqlDeco(this._1) === this._2 + } + +} + +object docs { + + extension StringOps for Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = map(_.length).max + filter(_.length == maxLength) + } + } + + extension ListListOps[T] for List[List[T]] { + def flattened: List[T] = foldLeft[List[T]](Nil)(_ ++ _) + } + + extension OrdSeqOps[T : math.Ordering] for Seq[T] { + def indexOfLargest = zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = zipWithIndex.minBy(_._1)._2 + } + + object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + extension Ensuring[T] for T { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } + } + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} + +import extensions._ +import extensions2._ +object Test extends App { + val c = Circle(0, 1, 2) + println(c.area) + + implicit object IntHasEql extends Eql[Int] { + def eql (x: Int, y: Int): Boolean = x == y + } + + println(1 ~ "a") + + val r1 = Rectangle(0, 0, 2, 2) + val r2 = Rectangle(0, 0, 2, 3) + println(r1.isSquare) + println(r2.isSquare) + println(r2.isNotSquare) + println(r1.isNotSquare) + println(r1 === r1) + println(r1 === r2) + println(1 ==== 1) + println(1 ==== 2) + println(List(1, 2, 3).second) + println(List(List(1), List(2, 3)).flattened) + println(List(List(1), List(2, 3)).flattened.maxx) + println(Array(1, 2, 3).maxx) + println((2, 3).isSame) + println(EqlDeco((3, 3)).isSame) +}