diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 30dad2f44847..7de9ac97e27d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -4,6 +4,7 @@ package ast import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ +import util.Chars.isIdentifierPart import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._, transform.SymUtils._ import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} @@ -11,6 +12,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 +156,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 +192,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 +301,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 +758,136 @@ object desugar { Bind(name, Ident(nme.WILDCARD)).withPos(tree.pos) } + def decomposeTypePattern(tree: Tree)(implicit ctx: Context): (Tree, List[TypeDef]) = { + val bindingsBuf = new ListBuffer[TypeDef] + val elimTypeDefs = new untpd.UntypedTreeMap { + override def transform(tree: Tree)(implicit ctx: Context) = tree match { + case tree: TypeDef => + bindingsBuf += tree + Ident(tree.name).withPos(tree.pos) + case _ => + super.transform(tree) + } + } + (elimTypeDefs.transform(tree), bindingsBuf.toList) + } + + private val collectNames = new untpd.UntypedTreeAccumulator[ListBuffer[String]] { + def add(buf: ListBuffer[String], name: Name): ListBuffer[String] = { + val alpha = name.toString.filter(isIdentifierPart) + if (alpha.isEmpty) buf else buf += alpha + } + override def apply(buf: ListBuffer[String], tree: Tree)(implicit ctx: Context): ListBuffer[String] = tree match { + case Ident(name) => + add(buf, name) + case Select(qual, name) => + add(apply(buf, qual), name) + case TypeDef(name, rhs) => + apply(add(buf += "type", name), rhs) + case Apply(fn, _) => + apply(buf, fn) + case _ => + foldOver(buf, tree) + } + } + + private def extensionName(ext: Extension)(implicit ctx: Context): TypeName = { + var buf = collectNames(new ListBuffer[String] += "extend", ext.extended) + if (ext.impl.parents.nonEmpty) + buf = collectNames(buf += "", ext.impl.parents) + buf.toList.mkString("_").toTypeName + } + + /** extend : { } } + * -> + * implicit class ($this: ) + * extends { } + * + * where + * + * = concatenation of all alphanumeric characters between and including `extend` and `{`, + * mapping every sequence of other characters to a single `_`. + * + * (, ) = decomposeTypePattern() + * (, ) = desugarTypeBindings() + * = concatenated with in one clause + * = with each occurrence of unqualified `this` substituted by `$this`. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * extend { } + * -> + * implicit class ($this: ) + * extends AnyVal { } + * + * where + * + * = where each method definition gets as last parameter section. + * , are as above. + */ + def extension(tree: Extension)(implicit ctx: Context): Tree = { + val Extension(extended, impl) = tree + val isSimpleExtension = impl.parents.isEmpty + val (decorated, bindings) = decomposeTypePattern(extended) + val (typeParams, evidenceParams) = + desugarTypeBindings(bindings, forPrimaryConstructor = !isSimpleExtension) + val extName = extensionName(tree) + + val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) + var constr1 = + cpy.DefDef(impl.constr)( + tparams = typeParams.map(_.withFlags(Param | Private | Local)), + vparamss = (firstParam :: Nil) :: impl.constr.vparamss) + var parents1 = impl.parents + var body1 = substThis.transform(impl.body) + if (isSimpleExtension) { + constr1 = cpy.DefDef(constr1)(vparamss = constr1.vparamss.take(1)) + parents1 = ref(defn.AnyValType) :: Nil + body1 = body1.map { + case ddef: DefDef => + def resetFlags(vdef: ValDef) = + vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) + val originalParams = impl.constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) + case other => + other + } + } + else + constr1 = addEvidenceParams(constr1, evidenceParams) + + val mods = + if (isSimpleExtension) EmptyModifiers + else EmptyModifiers.withAddedMod(Mod.InstanceDecl()) + val icls = + TypeDef(extName, + cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) + .withMods(mods.withFlags(Implicit)) + desugr.println(i"desugar $extended --> $icls") + classDef(icls) + } + + private val substThis = new UntypedTreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case This(Ident(tpnme.EMPTY)) => Ident(nme.SELF).withPos(tree.pos) + case _ => super.transform(tree) + } + } + + private val leadingName = new UntypedTreeAccumulator[String] { + override def apply(x: String, tree: Tree)(implicit ctx: Context): String = + if (x.isEmpty) + tree match { + case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre) + case tree: RefTree if tree.name.isTypeName => tree.name.toString + case tree: TypeDef => tree.name.toString + case tree: Tuple => "Tuple" + case tree: Function => "Function" + case _ => foldOver(x, tree) + } + else x + } + def defTree(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: ValDef => valDef(tree) case tree: TypeDef => if (tree.isClassDef) classDef(tree) else tree @@ -757,6 +896,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) + case tree: Extension => extension(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/ast/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 252b301c2954..471e3184cbee 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,6 +40,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } + /** extend extended impl */ + case class Extension(extended: Tree, impl: Template) extends DefTree + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -124,6 +127,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 +142,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Enum() extends Mod(Flags.EmptyFlags) case class EnumCase() extends Mod(Flags.EmptyFlags) + + case class InstanceDecl() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions @@ -409,6 +416,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)(extended: Tree, impl: Template) = tree match { + case tree: Extension if (extended eq tree.extended) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Extension(extended, impl)) + } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index beae798c324a..1dff187f0000 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -17,6 +17,7 @@ object Printers { val checks: Printer = noPrinter val config: Printer = noPrinter val cyclicErrors: Printer = noPrinter + val desugr: Printer = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 872f7bd6302e..c4cef42e5f0a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -527,6 +527,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") @@ -1154,6 +1155,7 @@ class Definitions { AnyRefAlias, 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..c9e2f1926fb6 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 @@ -441,12 +446,12 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = commonFlags(Private, Protected, Abstract, Final, Inline, - Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) + Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased, Opaque) /** Flags representing modifiers that can appear in trees */ final val ModifierFlags = - SourceModifierFlags | Module | Param | Synthetic | Package | Local | - commonFlags(Mutable) + SourceModifierFlags | Module | Param | Synthetic | Package | Local + // | Mutable is subsumed by commonFlags(Opaque) from SourceModifierFlags // | Trait is subsumed by commonFlags(Lazy) from SourceModifierFlags assert(ModifierFlags.isTermFlags && ModifierFlags.isTypeFlags) @@ -457,7 +462,7 @@ object Flags { /** Flags guaranteed to be set upon symbol creation */ final val FromStartFlags = Module | Package | Deferred | MethodOrHKCommon | Param | ParamAccessor.toCommonFlags | - Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | + Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | Erroneous | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 1e64ff94d872..ea1bf83e2c67 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" @@ -246,6 +246,7 @@ object StdNames { // Compiler-internal val ANYname: N = "" + val COMPANION: N = "" val CONSTRUCTOR: N = "" val STATIC_CONSTRUCTOR: N = "" val DEFAULT_CASE: N = "defaultCase$" @@ -254,17 +255,6 @@ object StdNames { val FAKE_LOCAL_THIS: N = "this$" val LAZY_FIELD_OFFSET: N = "OFFSET$" val LAZY_SLOW_SUFFIX: N = "$lzycompute" - val UNIVERSE_BUILD_PREFIX: N = "$u.build." - val UNIVERSE_BUILD: N = "$u.build" - val UNIVERSE_PREFIX: N = "$u." - val UNIVERSE_SHORT: N = "$u" - val MIRROR_PREFIX: N = "$m." - val MIRROR_SHORT: N = "$m" - val MIRROR_UNTYPED: N = "$m$untyped" - val REIFY_FREE_PREFIX: N = "free$" - val REIFY_FREE_THIS_SUFFIX: N = "$this" - val REIFY_FREE_VALUE_SUFFIX: N = "$value" - val REIFY_SYMDEF_PREFIX: N = "symdef$" val OUTER: N = "$outer" val REFINE_CLASS: N = "" val ROOTPKG: N = "_root_" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 955f1a34342c..3ecb977bf7da 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 b7ea26776bd4..29e986fc6728 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -187,15 +187,6 @@ trait Symbols { this: Context => val companionMethodFlags = Flags.Synthetic | Flags.Private | Flags.Method - def synthesizeCompanionMethod(name: Name, target: SymDenotation, owner: SymDenotation)(implicit ctx: Context) = - if (owner.exists && target.exists && !owner.unforcedIsAbsent && !target.unforcedIsAbsent) { - val existing = owner.unforcedDecls.lookup(name) - - existing.orElse{ - ctx.newSymbol(owner.symbol, name, companionMethodFlags , ExprType(target.typeRef)) - } - } else NoSymbol - /** Create a package symbol with associated package class * from its non-info fields and a lazy type for loading the package's members. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 2611d96df182..627672102dde 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -759,9 +759,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - if (tyconIsTypeRef) recur(tp1, tp2.superType) + if (followSuperType) recur(tp1, tp2.superType) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -770,12 +770,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case param2: TypeParamRef => isMatchingApply(tp1) || canConstrain(param2) && canInstantiate(param2) || - compareLower(bounds(param2), tyconIsTypeRef = false) + compareLower(bounds(param2), followSuperType = false) case tycon2: TypeRef => isMatchingApply(tp1) || { tycon2.info match { case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) + val gbounds2 = ctx.gadt.bounds(tycon2.symbol) + if (gbounds2 == null) compareLower(info2, followSuperType = true) + else compareLower(gbounds2 & info2, followSuperType = false) case info2: ClassInfo => val base = tp1.baseType(info2.cls) if (base.exists && base.ne(tp1)) @@ -807,8 +809,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } canConstrain(param1) && canInstantiate || isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow) - case tycon1: TypeRef if tycon1.symbol.isClass => - false + case tycon1: TypeRef => + !tycon1.symbol.isClass && { + val gbounds1 = ctx.gadt.bounds(tycon1.symbol) + if (gbounds1 == null) recur(tp1.superType, tp2) + else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2) + } case tycon1: TypeProxy => recur(tp1.superType, tp2) case _ => diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 74d965ef48ac..0b969a4c1cc8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -163,9 +163,6 @@ object TypeErasure { * - For $asInstanceOf : [T]T * - For $isInstanceOf : [T]Boolean * - For all abstract types : = ? - * - For companion methods : the erasure of their type with semiEraseVCs = false. - * The signature of these methods are used to keep a - * link between companions and should not be semi-erased. * - For Java-defined symbols: : the erasure of their type with isJava = true, * semiEraseVCs = false. Semi-erasure never happens in Java. * - For all other symbols : the semi-erasure of their types, with @@ -173,7 +170,7 @@ object TypeErasure { */ def transformInfo(sym: Symbol, tp: Type)(implicit ctx: Context): Type = { val isJava = sym is JavaDefined - val semiEraseVCs = !isJava && !sym.isCompanionMethod + val semiEraseVCs = !isJava val erase = erasureFn(isJava, semiEraseVCs, sym.isConstructor, wildcardOK = false) def eraseParamBounds(tp: PolyType): Type = diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c85cbcdf2622..0802fb7eb898 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 { @@ -508,7 +508,9 @@ object Types { case tp1 => tp1 }) case tp: TypeRef => - tp.denot.findMember(name, pre, excluded) + val mbr = tp.denot.findMember(name, pre, excluded) + if (mbr.exists) mbr + else followGADT.findMember(name, pre, excluded) case tp: AppliedType => goApplied(tp) case tp: ThisType => @@ -989,7 +991,7 @@ object Types { case tp: TypeRef => if (tp.symbol.isClass) tp else tp.info match { - case TypeAlias(tp) => tp.dealias1(keepAnnots): @tailrec + case TypeAlias(alias) => alias.dealias1(keepAnnots): @tailrec case _ => tp } case app @ AppliedType(tycon, args) => @@ -1283,6 +1285,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 @@ -2115,7 +2134,13 @@ object Types { override def designator = myDesignator override protected def designator_=(d: Designator) = myDesignator = d - override def underlying(implicit ctx: Context): Type = info + override def underlying(implicit ctx: Context): Type = { + if (symbol.is(Opaque)) { + val gadtBounds = ctx.gadt.bounds(symbol) + if (gadtBounds != null) return gadtBounds + } + info + } } final class CachedTermRef(prefix: Type, designator: Designator, hc: Int) extends TermRef(prefix, designator) { @@ -2184,7 +2209,7 @@ object Types { if (ctx.erasedTypes) tref else cls.info match { case cinfo: ClassInfo => cinfo.selfType - case cinfo: ErrorType if ctx.mode.is(Mode.Interactive) => cinfo + case _: ErrorType | NoType if ctx.mode.is(Mode.Interactive) => cls.info // can happen in IDE if `cls` is stale } diff --git a/compiler/src/dotty/tools/dotc/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 04c4c2e9d4bc..180c14922588 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 @@ -227,7 +228,7 @@ object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) val MajorVersion = 4 - val MinorVersion = 0 + val MinorVersion = 1 /** Tags used to serialize names */ class NameTags { @@ -299,6 +300,7 @@ object TastyFormat { final val DEFAULTparameterized = 30 final val STABLE = 31 final val MACRO = 32 + final val OPAQUE = 33 // Cat. 2: tag Nat @@ -410,7 +412,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= MACRO || + firstSimpleTreeTag <= tag && tag <= OPAQUE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -432,6 +434,7 @@ object TastyFormat { | OVERRIDE | INLINE | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -486,6 +489,7 @@ object TastyFormat { case OVERRIDE => "OVERRIDE" case INLINE => "INLINE" case MACRO => "MACRO" + case OPAQUE => "OPAQUE" case STATIC => "STATIC" case OBJECT => "OBJECT" case TRAIT => "TRAIT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f9652f19a4d8..36dfe4cd0977 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -606,6 +606,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Trait) writeByte(TRAIT) if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) + if (flags is Opaque) writeByte(OPAQUE) } sym.annotations.foreach(pickleAnnotation(sym, _)) } @@ -616,8 +617,10 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot - // 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 7f64f2cfb941..e608e03cfefb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -528,6 +528,7 @@ class TreeUnpickler(reader: TastyReader, // avoids space leaks by not capturing the current context forkAt(rhsStart).readTerm() }) + sym.normalizeOpaque() goto(start) sym } @@ -562,6 +563,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) @@ -738,12 +740,9 @@ class TreeUnpickler(reader: TastyReader, // The only case to check here is if `sym` is a root. In this case // `companion` might have been entered by the environment but it might // be missing from the Tasty file. So we check explicitly for that. - def isCodefined = - roots.contains(companion.denot) == seenRoots.contains(companion) - if (companion.exists && isCodefined) { - if (sym is Flags.ModuleClass) sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion) - else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion) - } + def isCodefined = roots.contains(companion.denot) == seenRoots.contains(companion) + + if (companion.exists && isCodefined) sym.registerCompanion(companion) TypeDef(readTemplate(localCtx)) } else { sym.info = TypeBounds.empty // needed to avoid cyclic references when unpicklin rhs, see i3816.scala diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index d1019095edeb..8e60515ffdae 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/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7e4d94dc1f93..24726a62e964 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -323,6 +323,20 @@ object Parsers { finally inEnum = saved } + private[this] var inTypePattern = false + private[this] var inBindingTypePattern = false + private def withinTypePattern[T](binding: Boolean)(body: => T): T = { + val savedInType = inTypePattern + val savedInBinding = inBindingTypePattern + inTypePattern = true + if (binding) inBindingTypePattern = true + try body + finally { + inTypePattern = savedInType + inBindingTypePattern = savedInBinding + } + } + def migrationWarningOrError(msg: String, offset: Int = in.offset) = if (in.isScala2Mode) ctx.migrationWarning(msg, source atPos Position(offset)) @@ -917,7 +931,16 @@ object Parsers { else Nil first :: rest } - def typParser() = if (wildOK) typ() else toplevelTyp() + def typParser() = + if (in.token == TYPE && inTypePattern) + if (inBindingTypePattern) + typeParamCore(in.skipToken(), isConcreteOwner = true) + else + atPos(in.skipToken(), nameStart) { + Bind(ident().toTypeName, Ident(nme.WILDCARD)) + } + else if (wildOK) typ() + else toplevelTyp() if (namedOK && in.token == IDENTIFIER) typParser() match { case Ident(name) if in.token == EQUALS => @@ -998,7 +1021,7 @@ object Parsers { def typeDependingOn(location: Location.Value): Tree = if (location == Location.InParens) typ() - else if (location == Location.InPattern) refinedType() + else if (location == Location.InPattern) withinTypePattern(binding = false)(refinedType()) else infixType() /** Checks whether `t` is a wildcard type. @@ -1058,7 +1081,7 @@ object Parsers { * | PostfixExpr `match' `{' CaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] - * Ascription ::= `:' CompoundType + * Ascription ::= `:' InfixType * | `:' Annotation {Annotation} * | `:' `_' `*' */ @@ -1178,6 +1201,13 @@ object Parsers { t } + /** Ascription ::= `:' InfixType + * | `:' Annotation {Annotation} + * | `:' `_' `*' + * PatternAscription ::= `:' TypePattern + * | `:' `_' `*' + * TypePattern ::= RefinedType + */ def ascription(t: Tree, location: Location.Value) = atPos(startOffset(t)) { in.skipToken() in.token match { @@ -1539,7 +1569,7 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() } else Nil - /** Pattern1 ::= PatVar Ascription + /** Pattern1 ::= PatVar PatternAscription * | Pattern2 */ def pattern1(): Tree = { @@ -1649,6 +1679,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. @@ -1773,15 +1804,13 @@ object Parsers { /* -------- PARAMETERS ------------------------------------------- */ /** ClsTypeParamClause::= `[' ClsTypeParam {`,' ClsTypeParam} `]' - * ClsTypeParam ::= {Annotation} [`+' | `-'] - * id [HkTypeParamClause] TypeParamBounds - * + * ClsTypeParam ::= {Annotation} [`+' | `-'] TypeParamCore * DefTypeParamClause::= `[' DefTypeParam {`,' DefTypeParam} `]' - * DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds + * DefTypeParam ::= {Annotation} TypeParamCore + * TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds * * TypTypeParamCaluse::= `[' TypTypeParam {`,' TypTypeParam} `]' * TypTypeParam ::= {Annotation} id [HkTypePamClause] TypeBounds - * * HkTypeParamClause ::= `[' HkTypeParam {`,' HkTypeParam} `]' * HkTypeParam ::= {Annotation} ['+' | `-'] (id [HkTypePamClause] | _') TypeBounds */ @@ -1801,23 +1830,26 @@ object Parsers { else EmptyFlags } } - atPos(start, nameStart) { - val name = - if (isConcreteOwner || in.token != USCORE) ident().toTypeName - else { - in.nextToken() - WildcardParamName.fresh().toTypeName - } - val hkparams = typeParamClauseOpt(ParamOwner.TypeParam) - val bounds = - if (isConcreteOwner) typeParamBounds(name) - else typeBounds() - TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) - } + typeParamCore(start, isConcreteOwner).withMods(mods) } commaSeparated(() => typeParam()) } + /** TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds */ + def typeParamCore(start: Offset, isConcreteOwner: Boolean): TypeDef = atPos(start, nameStart) { + val name = + if (in.token == USCORE && !isConcreteOwner) { + in.nextToken() + WildcardParamName.fresh().toTypeName + } + else ident().toTypeName + val hkparams = typeParamClauseOpt(ParamOwner.TypeParam) + val bounds = + if (isConcreteOwner) typeParamBounds(name) + else typeBounds() + TypeDef(name, lambdaAbstract(hkparams, bounds)) + } + def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] = if (in.token == LBRACKET) typeParamClause(ownerKind) else Nil @@ -1830,8 +1862,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 +1911,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 +1924,8 @@ object Parsers { } } funArgMods() - + if (ofExtension && !imods.is(Implicit)) + syntaxError(i"parameters of extension must be implicit") commaSeparated(() => param()) } } @@ -1900,7 +1935,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 } @@ -2179,7 +2214,7 @@ object Parsers { } } - /** ClassDef ::= id ClassConstr TemplateOpt + /** ClassDef ::= id ClassConstr [TemplateClause] */ def classDef(start: Offset, mods: Modifiers): TypeDef = atPos(start, nameStart) { classDefRest(start, mods, ident().toTypeName) @@ -2187,7 +2222,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 +2231,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 +2240,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 +2257,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 +2303,35 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } + /** Extension ::= ‘extend’ BindingTypePattern + * [[nl] ImplicitParamClause] ExtensionClause + * BindingTypePattern ::= AnnotType + * ExtensionClause ::= : Template + * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + */ + def extension(): Extension = atPos(in.skipToken(), nameStart) { + val extended = withinTypePattern(binding = true)(annotType()) + val vparamss = paramClauses(tpnme.EMPTY, ofExtension = true).take(1) + val constr = makeConstructor(Nil, vparamss) + val templ = + if (in.token == COLON) { + in.nextToken() + template(constr, bodyRequired = true)._1 + } + else { + if (in.token == EXTENDS) syntaxError("`:` or `{` expected") + val templ = templateClauseOpt(constr, bodyRequired = true) + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos.orElse(templ.pos.startPos)) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + templ + } + Extension(extended, templ) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2284,26 +2348,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) } @@ -2379,6 +2447,7 @@ object Parsers { * TemplateStat ::= Import * | Annotations Modifiers Def * | Annotations Modifiers Dcl + * | Extension * | Expr1 * | * EnumStat ::= TemplateStat @@ -2409,6 +2478,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == EXTEND) + stats += extension() else if (isExprIntro) stats += expr1() else if (isDefIntro(modifierTokensOrCase)) @@ -2453,6 +2524,7 @@ object Parsers { * BlockStat ::= Import * | Annotations [implicit] [lazy] Def * | Annotations LocalModifiers TmplDef + * | Extension * | Expr1 * | */ @@ -2463,6 +2535,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == EXTEND) + stats += extension() else if (isExprIntro) stats += expr(Location.InBlock) else if (isDefIntro(localModifierTokens)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 0cc0cc16fcea..1f5d74127ea6 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 EXTEND = 66; enter(EXTEND, "extend") /** 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, EXTEND) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -226,7 +228,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED) + ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, LAZY, ERASED, OPAQUE) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) @@ -238,7 +240,7 @@ object Tokens extends TokensCommon { /** Is token only legal as start of statement (eof also included)? */ final val mustStartStatTokens = defIntroTokens | modifierTokens | BitSet( - IMPORT, PACKAGE) + IMPORT, PACKAGE, EXTEND) final val canStartStatTokens = canStartExpressionTokens | mustStartStatTokens | BitSet( AT, CASE) diff --git a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala index 392d719cfefd..c590adf2206e 100644 --- a/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala @@ -13,7 +13,7 @@ import scala.language.implicitConversions class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { override protected def filterModTextAnnots(annots: List[untpd.Tree]): List[untpd.Tree] = - annots.filter(_.tpe != defn.SourceFileAnnotType) + super.filterModTextAnnots(annots).filter(_.tpe != defn.SourceFileAnnotType) override protected def blockText[T >: Untyped](trees: List[Trees.Tree[T]]): Text = { super.blockText(trees.filterNot(_.isInstanceOf[Closure[_]])) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 48bc992f8a47..a5bceb99dbcc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -8,6 +8,7 @@ import Flags._ import Names._ import Symbols._ import NameOps._ +import NameKinds.ExpandedName import Constants._ import TypeErasure.ErasedValueType import Contexts.Context @@ -15,7 +16,7 @@ import Scopes.Scope import Denotations._ import SymDenotations._ import Annotations.Annotation -import StdNames.{nme, tpnme} +import StdNames.{nme, tpnme, str} import ast.{Trees, tpd, untpd} import typer.{Implicits, Inliner, Namer} import typer.ProtoTypes._ @@ -721,6 +722,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case info: ImportType => return s"import $info.expr.show" case _ => } + if (sym.is(ModuleClass)) kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym)) else diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 312e2d4093db..9106772c0801 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -212,8 +212,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // Synthetic methods that are always present do not affect the API // and can therefore be ignored. - def alwaysPresent(s: Symbol) = - s.isCompanionMethod || (csym.is(ModuleClass) && s.isConstructor) + def alwaysPresent(s: Symbol) = csym.is(ModuleClass) && s.isConstructor val decls = cinfo.decls.filter(!alwaysPresent(_)) val apiDecls = apiDefinitions(decls) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 3240300d8f52..14ece431c33b 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -15,6 +15,7 @@ import Contexts.Context import Symbols._ import SymDenotations._ import Decorators._ +import Annotations._ import dotty.tools.dotc.core.Annotations.ConcreteAnnotation import dotty.tools.dotc.core.Denotations.SingleDenotation import scala.collection.mutable @@ -30,13 +31,14 @@ import StdNames._ * - eliminates some kinds of trees: Imports, NamedArgs * - stubs out native methods * - eliminates self tree in Template and self symbol in ClassInfo + * - rewrites opaque type aliases to normal alias types * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types * - drops branches of ifs using the rules * if (true) A else B ==> A * if (false) A else B ==> B */ -class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => +class FirstTransform extends MiniPhase with SymTransformer { thisPhase => import ast.tpd._ override def phaseName = "firstTransform" @@ -53,12 +55,22 @@ 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 def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { diff --git a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala index 484e538e0bf4..98a1e57d138b 100644 --- a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala +++ b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala @@ -34,7 +34,8 @@ class MoveStatics extends MiniPhase with SymTransformer { override def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] = { if (ctx.owner.is(Flags.Package)) { val (classes, others) = trees.partition(x => x.isInstanceOf[TypeDef] && x.symbol.isClass) - val pairs = classes.groupBy(_.symbol.name.stripModuleClassSuffix).asInstanceOf[Map[Name, List[TypeDef]]] + // TODO make a groupBy that builds linked maps + val pairs = classes.groupBy(_.symbol.name.stripModuleClassSuffix).asInstanceOf[Map[Name, List[TypeDef]]].toList.sortBy(_._1.toString) def rebuild(orig: TypeDef, newBody: List[Tree]): Tree = { if (orig eq null) return EmptyTree @@ -73,7 +74,7 @@ class MoveStatics extends MiniPhase with SymTransformer { if (classes.head.symbol.is(Flags.Module)) move(classes.head, null) else List(rebuild(classes.head, classes.head.rhs.asInstanceOf[Template].body)) else move(classes.head, classes.tail.head) - Trees.flatten(newPairs.toList.flatten ++ others) + Trees.flatten(newPairs.flatten ++ others) } else trees } } 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 6be342739f75..30f04aee8ccb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -370,10 +370,7 @@ class TreeChecker extends Phase with SymTransformer { checkOwner(impl.constr) def isNonMagicalMethod(x: Symbol) = - x.is(Method) && - !x.isCompanionMethod && - !x.isValueClassConvertMethod && - !(x.is(Macro) && ctx.phase.refChecked) + x.is(Method) && !x.isValueClassConvertMethod && !(x.is(Macro) && ctx.phase.refChecked) val symbolsNotDefined = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 942628519545..527376594559 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -379,6 +379,7 @@ object Checking { checkNoConflict(Lazy, Inline) if (sym.is(Inline)) checkApplicable(Inline, sym.isTerm && !sym.is(Mutable | Module)) if (sym.is(Lazy)) checkApplicable(Lazy, !sym.is(Method | Mutable)) + if (sym.is(Opaque)) checkApplicable(Opaque, sym.isAliasType) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { fail(CannotHaveSameNameAs(sym, cls, CannotHaveSameNameAs.CannotBeOverridden)) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 31f23a3327e5..1f25b7130958 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -393,7 +393,7 @@ trait ImplicitRunInfo { self: Run => override implicit protected val ctx: Context = liftingCtx override def stopAtStatic = true def apply(tp: Type) = tp match { - case tp: TypeRef if tp.symbol.isAbstractOrAliasType => + case tp: TypeRef if !tp.symbol.canHaveCompanion => val pre = tp.prefix def joinClass(tp: Type, cls: ClassSymbol) = AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner)) @@ -401,7 +401,7 @@ trait ImplicitRunInfo { self: Run => (lead /: tp.classSymbols)(joinClass) case tp: TypeVar => apply(tp.underlying) - case tp: AppliedType if !tp.tycon.typeSymbol.isClass => + case tp: AppliedType if !tp.tycon.typeSymbol.canHaveCompanion => def applyArg(arg: Type) = arg match { case TypeBounds(lo, hi) => AndType.make(lo, hi) case WildcardType(TypeBounds(lo, hi)) => AndType.make(lo, hi) @@ -439,21 +439,24 @@ trait ImplicitRunInfo { self: Run => case tp: NamedType => val pre = tp.prefix comps ++= iscopeRefs(pre) - def addClassScope(cls: ClassSymbol): Unit = { - def addRef(companion: TermRef): Unit = { - val compSym = companion.symbol - if (compSym is Package) - addRef(companion.select(nme.PACKAGE)) - else if (compSym.exists) - comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] - } - def addParentScope(parent: Type): Unit = - iscopeRefs(tp.baseType(parent.typeSymbol)) foreach addRef - val companion = cls.companionModule + def addRef(companion: TermRef): Unit = { + val compSym = companion.symbol + if (compSym is Package) + addRef(companion.select(nme.PACKAGE)) + else if (compSym.exists) + comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] + } + def addCompanionOf(sym: Symbol) = { + val companion = sym.companionModule if (companion.exists) addRef(companion.termRef) - cls.classParents foreach addParentScope } - tp.classSymbols(liftingCtx) foreach addClassScope + def addClassScope(cls: ClassSymbol): Unit = { + addCompanionOf(cls) + for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.typeSymbol))) + addRef(ref) + } + if (tp.widen.typeSymbol.is(Opaque)) addCompanionOf(tp.widen.typeSymbol) + else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 69836719c7d3..1adc47548004 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -101,14 +101,32 @@ trait NamerContextOps { this: Context => if (owner.exists) freshCtx.setOwner(owner) else freshCtx } + /** If `owner` is a companion object of an opaque type, record the alias + * in the GADT bounds of `freshCtx. + */ + def handleOpaqueCompanion(freshCtx: FreshContext, owner: Symbol): FreshContext = { + if (owner.is(Module)) { + val opaq = owner.companionOpaqueType + val alias = opaq.opaqueAlias + if (alias.exists) { + println(i"set GADT bounds of $opaq : $alias") + val result = freshCtx.setFreshGADTBounds + result.gadt.setBounds(opaq, TypeAlias(alias)) + result + } + else freshCtx + } + else freshCtx + } + /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - 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 +524,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 +630,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 +856,7 @@ class Namer { typer: Typer => addInlineInfo(denot) denot.info = typeSig(sym) Checking.checkWellFormed(sym) + denot.normalizeOpaque() denot.info = avoidPrivateLeaks(sym, sym.pos) } } @@ -929,7 +944,8 @@ class Namer { typer: Typer => if (cls.isRefinementClass) ptype else { val pt = checkClassType(ptype, parent.pos, - traitReq = parent ne parents.head, stablePrefixReq = true) + traitReq = (parent `ne` parents.head) || original.mods.hasMod[Mod.InstanceDecl], + 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 54db6540d49d..ac8ae2ab0875 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -6,6 +6,7 @@ import core._ import ast._ import Contexts._, Types._, Flags._, Denotations._, Names._, StdNames._, NameOps._, Symbols._ import NameKinds.DepParamName +import SymDenotations.NoDenotation import Trees._ import Constants._ import Scopes._ @@ -104,8 +105,9 @@ object ProtoTypes { memberProto.isRef(defn.UnitClass) || compat.normalizedCompatible(NamedType(tp1, name, m), memberProto) // Note: can't use `m.info` here because if `m` is a method, `m.info` - // loses knowledge about `m`'s default arguments. + // loses knowledge about `m`'s default arguments. || mbr match { // hasAltWith inlined for performance + case NoDenotation => tp1.exists && isMatchedBy(tp1.followGADT) case mbr: SingleDenotation => mbr.exists && qualifies(mbr) case _ => mbr hasAltWith qualifies } @@ -282,6 +284,9 @@ object ProtoTypes { def isDropped: Boolean = toDrop + override def isErroneous(implicit ctx: Context): Boolean = + myTypedArgs.tpes.exists(_.widen.isErroneous) + override def toString = s"FunProto(${args mkString ","} => $resultType)" def map(tm: TypeMap)(implicit ctx: Context): FunProto = diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2a0bafe31a2d..afb0a5642999 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -343,7 +343,10 @@ object RefChecks { if (autoOverride(member) || other.owner.is(JavaTrait) && ctx.testScala2Mode("`override' modifier required when a Java 8 default method is re-implemented", member.pos)) member.setFlag(Override) - else if (member.owner != clazz && other.owner != clazz && !(other.owner derivesFrom member.owner)) + else if (member.isType && self.memberInfo(member) =:= self.memberInfo(other)) + () // OK, don't complain about type aliases which are equal + else if (member.owner != clazz && other.owner != clazz && + !(other.owner derivesFrom member.owner)) emitOverrideError( clazz + " inherits conflicting members:\n " + infoStringWithLocation(other) + " and\n " + infoStringWithLocation(member) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 0e283cd49e37..1ae290f91f2e 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 438b3ef9b279..f1c8eb5c1ac2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2087,8 +2087,11 @@ class Typer extends Namer noMatches } case alts => - val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) - errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + if (tree.tpe.isErroneous || pt.isErroneous) tree.withType(UnspecifiedErrorType) + else { + val remainingDenots = alts map (_.denot.asInstanceOf[SingleDenotation]) + errorTree(tree, AmbiguousOverload(tree, remainingDenots, pt)(err)) + } } } diff --git a/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/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 2e8aa21f3f55..bf09c24413e8 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -120,7 +120,8 @@ ClassQualifier ::= ‘[’ id ‘]’ Type ::= [FunArgMods] FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | InfixType -FunArgMods ::= { `implicit` | `erased` } + | ‘type’ TypeParamCore + (if inside a BindingTypePattern) FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | '(' TypedFunParam {',' TypedFunParam } ')' @@ -138,8 +139,10 @@ SimpleType ::= SimpleType TypeArgs | ‘_’ TypeBounds | Refinement RefinedTypeTree(EmptyTree, refinement) | SimpleLiteral SingletonTypeTree(l) -ArgTypes ::= Type {‘,’ Type} - | NamedTypeArg {‘,’ NamedTypeArg} +ArgTypes ::= ArgType {‘,’ ArgType} +ArgType ::= Type + | ‘type’ id + (if inside a TypePattern) FunArgType ::= Type | ‘=>’ Type PrefixOp(=>, t) ParamType ::= [‘=>’] ParamValueType @@ -209,6 +212,7 @@ Block ::= {BlockStat semi} [BlockResult] BlockStat ::= Import | {Annotation} [‘implicit’ | ‘lazy’] Def | {Annotation} {LocalModifier} TmplDef + | Extension | Expr1 ForExpr ::= ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’) ForYield(enums, expr) @@ -225,7 +229,7 @@ CaseClauses ::= CaseClause { CaseClause } CaseClause ::= ‘case’ (Pattern [Guard] ‘=>’ Block | INT) CaseDef(pat, guard?, block) // block starts at => Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) -Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) +Pattern1 ::= PatVar ‘:’ TypePattern Bind(name, Typed(Ident(wildcard), tpe)) | Pattern2 Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) @@ -238,19 +242,25 @@ SimplePattern1 ::= Path | SimplePattern1 ‘.’ id PatVar ::= varid | ‘_’ +TypePattern ::= RefinedType Patterns ::= Pattern {‘,’ Pattern} ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ Apply(fn, pats) | ‘(’ [Patterns ‘,’] Pattern2 ‘:’ ‘_’ ‘*’ ‘)’ + +Extension ::= ‘extend’ BindingTypePattern + [[nl] ImplicitParamClause] ExtensionClause Extension(name, templ) +ExtensionClause ::= ‘implements’ Template + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ +BindingTypePattern::= AnnotType ``` ### Type and Value Parameters ```ebnf ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’ -ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds) - id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) - +ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeParamCore TypeDef(Modifiers, name, tparams, bounds) DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ -DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds +DefTypeParam ::= {Annotation} TypeParamCore +TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds Bound(below, above, context) TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds @@ -261,6 +271,8 @@ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘)’] ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ +ImplicitParamClause + ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’) ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param @@ -286,8 +298,11 @@ LocalModifier ::= ‘abstract’ | ‘sealed’ | ‘implicit’ | ‘lazy’ + | ‘opaque’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ (id | ‘this’) ‘]’ +FunArgMods ::= { `implicit` | `erased` } +ImplicitMods ::= `implicit` [`erased`] | `erased` `implicit` Annotation ::= ‘@’ SimpleType {ParArgumentExprs} Apply(tpe, args) @@ -329,12 +344,12 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr TmplDef ::= ([‘case’] ‘class’ | trait’) ClassDef | [‘case’] ‘object’ ObjectDef | `enum' EnumDef -ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) -ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat +ClassDef ::= id ClassConstr [TemplateClause] TypeDef(mods, name, templ) +ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] -ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor -EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody EnumDef(mods, name, tparams, template) -TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] +ObjectDef ::= id [TemplateClause] ModuleDef(mods, name, template) // no constructor +EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody TypeDef(mods, name, template) +TemplateClause ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} ConstrApp ::= AnnotType {ArgumentExprs} Apply(tp, args) @@ -347,6 +362,7 @@ TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} TemplateStat ::= Import | {Annotation [nl]} {Modifier} Def | {Annotation [nl]} {Modifier} Dcl + | Extension | Expr1 | SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) diff --git a/docs/docs/reference/dropped/implicit-value-classes.md b/docs/docs/reference/dropped/implicit-value-classes.md new file mode 100644 index 000000000000..857a83bd23b0 --- /dev/null +++ b/docs/docs/reference/dropped/implicit-value-classes.md @@ -0,0 +1,35 @@ +--- +layout: doc-page +title: Dropped: Implicit Classes and Value Classes +--- + +Scala uses implicit classes to define extension methods and conversions. E.g., from `Predef.scala`: + +```scala +implicit final class ArrowAssoc[A](private val self: A) extends AnyVal { + def -> [B](y: B): (A, B) = (self, y) +} +``` + +Implicit classes and value classes are still supported in Dotty, in order to support cross compilation with Scala 2. But they will be phased out in the future. The `ArrowAssoc` class can be written as an [augmentation](../augments/method-augments.html) as follows: + +```scala +augment (type A) { + def -> [B](that: B): (A, B) = (this, that) +} +``` + +Most other uses of value classes can be expressed by [opaque type aliases](../opaque.html). +There are only two aspects of value classes that are not covered: + + - A user-definable `toString` method. E.g. if `Meter` is a value class implemented in terms of `Double`, it is possible to define `toString` to that `new Meter(10).toString` prints `10m` instead of `10`. + + - Type tests and checked type casts for value classes. E.g. if `x: Any` once can have a pattern match like this: + + x match { case m: Meter => ... } + + The match succeeds if `x` is a `Meter` but not if it is `Double`. By contrast, an implementation in terms of + opaque type aliases would flag the match with an unchecked warning and succeed for both. This is a consequence of using the same runtime representation for `Meter` and `Double`. + +Given the quite high complexity of value classes, in terms of rules what is legal and what is not, and in terms of their boxing model, it is attractive to drop them. The combinaton of opaque types and augmentations is both simpler and generally more pleasant to use. + diff --git a/docs/docs/reference/extend/extension-methods.md b/docs/docs/reference/extend/extension-methods.md new file mode 100644 index 000000000000..77ab8459ac6a --- /dev/null +++ b/docs/docs/reference/extend/extension-methods.md @@ -0,0 +1,184 @@ +--- +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) + +extend Circle { + def circumference: Double = this.radius * math.Pi * 2 +} +``` + +The `extend Circle` clause adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Meaning of `this` + +Inside an extension method, the name `this` stands for the receiver on which the +method is applied when it is invoked. E.g. in the application of `circle.circumference`, +the `this` in the body of `circumference` refers to `circle`. Unlike for regular methods, +an explicit `this` is mandatory to refer to members of the receiver. So the following +gives a compilation error: + +```scala + | def circumference = radius * math.Pi * 2 + | ^^^^^^ + | not found: radius +``` + +### Scope of Extend Clauses + +Extend clauses can appear anywhere in a program; there is no need to co-define them with the types they extend. Extension methods are available wherever their defining extend clause is in scope. Extend clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can wrap extend clauses in objects, like this: + +```scala +object circleOps { + extend Circle { + def circumference: Double = this.radius * math.Pi * 2 + def area: Double = this.radius * this.radius * math.Pi + } +} +... +import circleOps._ +``` + +### 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 +extend Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) + } +} +``` + +### Extended Type Patterns + +The previous example extended a specific instance of a generic type. It is also possible +to extend a generic type itself, using a _type pattern_: + +```scala +extend List[type T] { + def second: T = this.tail.head +} +``` + +The `type T` argument indicates that the extension applies to `List[T]`s for any type `T`. We also say that `type T` introduces `T` as a _variable_ in the type pattern `List[type T]`. Type variables may appear anywhere in a type pattern. Example: + +```scala +extend List[List[type T]] { + def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +### Type Patterns in Cases + +The `type ...` syntax for pattern bound type variables also applies to patterns in +case clauses of `match` and `try` expressions. For instance: + +```scala +def f[T](s: Set[T], x: T) = s match { + case _: SortedSet[type U] => ... // binds `U`, infers that `U = T` + case _ => +} +``` + +Previously, one used a lower-case name to indicate a variable in a type pattern, as in: + +```scala + case _: SortedSet[u] => ... // binds `u`, infers that `u = T` +``` + +While being more regular wrt term variables in patterns, this usage is harder to read, and has the problem that it feels unnatrual to have to write type names in lower case. It will therefore be phased out to be replaced by the explicit `type T` syntax. + +Type patterns in cases only come in unbounded form; the bounds defined in the next section are not applicable to them. + +### Bounds in Extended Type Patterns + +It is also possible to use bounds for the type variables in an extended type pattern. Examples: + +```scala +extend List[type T <: Shape] { + def totalArea = this.map(_.area).sum +} +``` + +Context-bounds are also supported: + +```scala +extend Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +### Implicit Parameters for Type Patterns + +The standard translation of context bounds expands the bound in the last example to an implicit _evidence_ parameter of type `math.Ordering[T]`. It is also possible to give evidence parameters explicitly. The following example is equivalent to the previous one: + +```scala +extend Seq[type T](implicit ev: math.Ordering[T]) { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +There can be only one parameter clause following a type pattern and it must be implicit. As usual, one can combine context bounds and implicit evidence parameters. + +### Toplevel Type Variables + +A type pattern consisting of a top-level typevariable introduces a fully generic extension. For instance, the following extension introduces `x ~ y` as an alias +for `(x, y)`: + +```scala +extend (type T) { + def ~ [U](that: U) = (this, that) +} +``` + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and 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) + + extend (type T) { + def ensuring(condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } +} +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } diff --git a/docs/docs/reference/extend/instance-declarations.md b/docs/docs/reference/extend/instance-declarations.md new file mode 100644 index 000000000000..17d6f30564f6 --- /dev/null +++ b/docs/docs/reference/extend/instance-declarations.md @@ -0,0 +1,78 @@ +--- +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 +} + +extend 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 circleOps($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 +extend (type T : Eql) : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + + + +### Syntax of Extensions + +The syntax of extensions is specified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + + Extension ::= ‘extend’ BindingTypePattern + [[nl] ImplicitParamClause] ExtensionClause + ExtensionClause ::= ‘:’ Template + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + + ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’ + ImplicitMods ::= `implicit` [`erased`] | `erased` `implicit` + + BindingTypePattern: := AnnotType + Type ::= ... + | ‘type’ TypeParamCore (if inside a BindingTypePattern) + TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds + +In this definition, type patterns and types share the same productions. However, the production + + Type ::= ‘type’ TypeParamCore + +is applicable only inside a `BindingTypePattern`. + + diff --git a/docs/docs/reference/extend/translation.md b/docs/docs/reference/extend/translation.md new file mode 100644 index 000000000000..24ddab52ea7d --- /dev/null +++ b/docs/docs/reference/extend/translation.md @@ -0,0 +1,105 @@ +--- +layout: doc-page +title: "Translation of Extensions" +--- + +Extensons are closely related to implicit classes and can be translated into them. In short, +an extension that just adds extension methods translates into an implicit value class whereas +an instance declaration translates +into a regular implicit class. The following sections sketch this translation. + +Conversely, it is conceivable (and desirable) to replace most usages of implicit classes and value classes by extensions and [opaque types](../opaques.html). We plan to [drop](../dropped/implicit-value-classes.html) +these constructs in future versions of the language. Once that is achieved, the translations described +below can be simply composed with the existing translations of implicit and value classes into the core language. It is +not necessary to retain implicit and value classes as an intermediate step. + + +### Decomposing Type Patterns + +First, define a function `decompose` to decompose a type pattern `TP` into a list of type binders `tparams` and a type `T`. Going from left to right, every type binder `type U ` in `TP` is added to `tparams` and is replaced by the reference `U` in `T`. For instance, the type pattern + +```scala +Map[type K <: AnyRef, List[type T]] +``` +would be decomposed into the binders `type K <: AnyRef` and `type T` and the type `Map[K, List[T]]`. + +### Computing an Extension Name + +Extensions are anonymous -- the first identifier given in an extension designates the type that is extended, not the name of the extension itself. The name of an extension class is computed instead in a predictable way from the extended type pattern and the implemented traits as follows: + + 1. Take the sequence of tokens starting with `extends` up to the opening brace of the templete definitions. + 2. Drop an implicit parameter list if one is given and also drop any value arguments to parent constructors. + 3. Drop any tokens that are not keywords of identifiers. + 4. In each token resulting from the previous step, drop all characters that are not legal parts of alphanumeric identifiers. This leaves all characters that satisfy the `java.lang.Character.isUnicodeIdentifierPart` predicate as well as `$`. Among Ascii characters, the retained characters are all letters, digits, as well as `_` and `$`. + 5. Concatenate all non-empty strings resulting from the previous step with `_` separators, + except use `__` (i.e. two underscores) between identifiers coming from the type pattern + and identifiers coming from the implemented traits (if there are any). + +It is an error if two extensions defined in the same scope have the same computed name. These double definition errors can always be avoided by grouping augments together or, as a last resort, defining suitable type aliases. The scheme gives stable names that do not depend on the order in which definitions are given. + +### Translation of Extension Methods + +Now, a assume an extension + + extend { } + +where `` can be absent. For simplicity assume that there are no context bounds in +type definitions of ``. This is not an essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. Let `(, )` be the decomposition of ``. +The extension can be translated to the following implicit value class: + + implicit class ($this: ) extends AnyVal { } + +Here `` is the computed extension name +and `` results from `` by augmenting any definition in with the parameters and +replacing any occurrence of `this` with `$this`. + +For example, the extension + +```scala +extend Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class extend_Seq_type_T_math_Ordering [T]($this: List[T]) extends AnyVal { + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = $this.zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Instance Declarations + +Now, assume an extension + + extend : { } + +Let again `(, )` be the decomposition of ``. This extension is translated to + + implicit class ($this: ) extends { } + +Again, `` is the computed extension name. +Also as before, `` is computed from `` by replacing any occurrence of `this` with `$this`. However, all +parameters in now stay on the class definition, instead of being distributed to all members in ``. This is necessary in general, since `` might contain value definitions or other statements that cannot be +parameterized. + +For example, the extension + +```scala +extend (type T: Eql) : HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +would be translated to + +```scala +implicit class extend_type_T_Eql__HasEql_T [T] + ($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) + } +} +``` diff --git a/docs/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 2aed6909d5ef..3d00df75d185 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -29,6 +29,14 @@ sidebar: url: docs/reference/dependent-function-types.html - title: Literal Singleton Types url: docs/reference/singleton-types.html + - title: Extend Clauses + subsection: + - title: Extension Methods + url: docs/reference/extend/extension-methods.html + - title: Instance Declarations + url: docs/reference/extend/instance-declarations.html + - title: Translation of Extensions + url: docs/reference/extend/translation.html - title: Enums subsection: - title: Enumerations @@ -47,6 +55,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 @@ -101,6 +111,8 @@ sidebar: url: docs/reference/dropped/auto-apply.html - title: Weak Conformance url: docs/reference/dropped/weak-conformance.html + - title: Implicit and Value Classes + url: docs/reference/dropped/implicit-value-classes.html - title: Contributing subsection: - title: Getting Started diff --git a/tests/neg-custom-args/i1754.scala b/tests/neg-custom-args/i1754.scala index 68f8f2edecb6..5171599e9460 100644 --- a/tests/neg-custom-args/i1754.scala +++ b/tests/neg-custom-args/i1754.scala @@ -2,7 +2,7 @@ case class One[T](fst: T) object Test { def bad[T](e: One[T]) = e match { - case foo: One[a] => + case foo: One[type A] => val t: T = e.fst val nok: Nothing = t // error } diff --git a/tests/neg/extensions.scala b/tests/neg/extensions.scala new file mode 100644 index 000000000000..fae4e46d6d78 --- /dev/null +++ b/tests/neg/extensions.scala @@ -0,0 +1,98 @@ +import Predef.{any2stringadd => _, _} +object extensions { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + extend Circle { + def circumference = this.radius * math.Pi * 2 + private val p = math.Pi // error: `def` expected + } + + type Circle2 = Circle + + extend Circle2 { + def circumference = radius * math.Pi * 2 // error: not found + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + abstract class HasAreaClass extends HasArea + + extend Circle : HasArea { + def area = this.radius * this.radius * math.Pi + } + + extend Circle2 extends HasArea {} // error: `:` or `{` expected // error: `def` expected + + extend Circle : HasAreaClass { // error: class HasAreaClass is not a trait + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + extend List[type T] { + type I = Int // error: `def` expected + def second = this.tail.head + } + +// Specific trait implementations + + extend List[Int] { self => // error: `def` expected + import java.lang._ // error: `def` expected + def maxx = (0 /: this)(_ `max` _) + } + + extend 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 + } + + extend Rectangle[type T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic extensions + + extend (type T) { + def ~[U](that: U): (T, U) = (this, that) + } +} + +object extensions1 { + extend List[List[type T]] { + def flattened: List[T] = (this :\ (Nil: List[T]))(_ ++ _) + } +} + +object extensions2 { + import extensions.Eql + // Nested generic arguments + + extend List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + extend (type T: Eql, T) { + def isSame: Boolean = this._1 === this._2 // error: === is not a member + } + +} + +import extensions1._ +import extensions2._ +object Test extends App { + println(List(List(1), List(2, 3)).flattened) // error: type error + note that implicit conversions are ambiguous +} \ No newline at end of file diff --git a/tests/neg/gadt-eval.scala b/tests/neg/gadt-eval.scala index ff113785a2fc..3bb5d58ad697 100644 --- a/tests/neg/gadt-eval.scala +++ b/tests/neg/gadt-eval.scala @@ -20,7 +20,7 @@ object Test { def eval2[T](e: Exp[T]): T = e match { case e: Lit => e.value - case e: Pair[t1, t2] => + case e: Pair[type t1, type t2] => (eval(e.fst), eval(e.fst)) // error: //-- [E007] Type Mismatch Error: tests/neg/gadt-eval.scala:24:6 ------------------ //24 | (eval(e.fst), eval(e.fst)) diff --git a/tests/neg/i2494.scala b/tests/neg/i2494.scala index 21c11242878d..d636e3f81d8c 100644 --- a/tests/neg/i2494.scala +++ b/tests/neg/i2494.scala @@ -1,2 +1,2 @@ -enum +enum // error: cyclic reference 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/blueSkyExtensions.scala b/tests/pending/pos/blueSkyExtensions.scala new file mode 100644 index 000000000000..fd79a92d599a --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions.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 + } + + extend Int : Monoid { + static def unit = 0 + def + (that: Int) = this + that + } + + extend String : Monoid { + static def unit = "" + def + (that: Int) = this ++ that + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extend List[type T : Ord] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + static def pure[A]: This[A] + def map[B](f: A => B): This[B] + } + + // 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)) + } + + extend List[type T] : Monad[T] { + static def pure[A] = Nil + + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + + extend (type T[X]: Monad[X])[T[type A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + 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] + } + + extend 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 = ... + } + + extend 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] = ... + } + + extend List[type T] : Iterable[T] { + static type This[A] = List[A] + static def empty = Nil + static def apply(xs: A*) = (xs /: Nil)(_ :: _) + + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyExtensions1.scala b/tests/pending/pos/blueSkyExtensions1.scala new file mode 100644 index 000000000000..8ef37616e589 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions1.scala @@ -0,0 +1,134 @@ +/** A blue sky sketch how one might evolve extensions to do type classes + * without the current gap between functional and OO patterns. + * Largely inspired by the way Rust does it. + * + * The main additions (to be worked out in detail) is a self type `This` and + * a mechanism that a trait can abstract over companions of classes that implement it. + * Companion types and methods are declared using `static` for now, just to + * pick some familiar notation. + * + * Ideas about `This` (still vague at the moment) + * + * - Treat it as an additional abstract type in a trait, prefixed by the name of the trait + * - An implementing (non-trait) class binds the `This` types of all the traits it implements + * to itself. + * - Later subclasses do not re-bind `This` types already bound by their superclasses. + * (so in that sense, `This`-binding is like trait parameterization, the first implementing + * classes determines the self type and the parameters of a trait) + * - Paramerized classes have parameterized `This` types (e.g. Functor below). + */ +import Predef.{any2stringadd => _, _} +object blueSkyExtensions { + +// Semigroup and Monoid + + trait SemiGroup { + def + (that: This): This + } + + trait Monoid extends SemiGroup { + static def unit: This + } + + extension IntMonoid for Int : Monoid { + static def unit = 0 + def + (that: Int) = this + that + } + + extension StringMonoid for String : Monoid { + static def unit = "" + def + (that: Int) = this ++ that + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extension ListOrd[T : Ord] for List[T] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + static def pure[A]: This[A] + def map[B](f: A => B): This[B] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.statics[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extension ListMonad[T] for List[T] : Monad[T] { + static def pure[A] = Nil + + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + + extension MonadFlatten[T[X]: Monad[X]] for T[T[A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + static def empty: This[A] + static def apply(xs: A*): This[A] + + def filter(p: A => Boolean): This[A] + } + + trait Iterable[A] extends MonoIterable[A] { + def map[B](f: A => B): This[B] + def flatMap[B](f: A => This[B]): This[B] + } + + extension StringMonoIterable for String : MonoIterable[Char] { + static type This[A] = String + static def empty = "" + static def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + + extension StringIterable for String : Iterable[Char] { + static type This[A] = IndexedSeq[A] + + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extension ListIterable[T] for List[T] : Iterable[T] { + static type This[A] = List[A] + static def empty = Nil + static def apply(xs: A*) = (xs /: Nil)(_ :: _) + + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyExtensions2.scala b/tests/pending/pos/blueSkyExtensions2.scala new file mode 100644 index 000000000000..e26a4940a1a5 --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions2.scala @@ -0,0 +1,187 @@ +/** A blue sky sketch how one might evolve extensions to do type classes + * without the current gap between functional and OO patterns. + * Largely inspired by the way Rust does it. + * + * The main additions (to be worked out in detail) is a self type `This` and + * a mechanism that a trait can abstract over companions of classes that implement it. + * Companion types and methods are declared using `object` for now, just to + * pick some familiar notation. + * + * Ideas about `This` (still vague at the moment) + * + * - Treat it as an additional abstract type in a trait, prefixed by the name of the trait + * - An implementing (non-trait) class binds the `This` types of all the traits it implements + * to itself. + * - Later subclasses do not re-bind `This` types already bound by their superclasses. + * (so in that sense, `This`-binding is like trait parameterization, the first implementing + * classes determines the self type and the parameters of a trait) + * - Paramerized classes have parameterized `This` types (e.g. Functor below). + */ +import Predef.{any2stringadd => _, _} +object blueSkyExtensions { + +// Semigroup and Monoid + + trait SemiGroup { + def add (that: This): This + } + + trait Monoid extends SemiGroup { + static def unit: This + } + + extend Int : Monoid { + def add (that: Int) = this + that + static def unit = 0 + } + + extend String : Monoid { + def add (that: Int) = this ++ that + static def unit = "" + } + + def sum[T: Monoid](xs: List[T]): T = + (instance[T, Monoid].unit /: xs)(_ `add` _) + +// ---> + { + trait TypeClass { + type This + type Static[This] + } + + trait Implementation[From, To <: TypeClass] { + type This = From + def statics: To # Static[From] + def inject(x: From): To { type This = From } + } + + trait SemiGroup extends TypeClass { + def + (that: This): This + } + + trait Monoid extends SemiGroup { + class Static[This] { def unit: This } + } + + implicit object extend_Int_Monoid extends Monoid#Static[Int] with Implementation[Int, Monoid] { + def unit: Int = 0 + def inject($this: Int) = new Monoid { + type This = Int + def + (that: This): This = $this + that + } + } + + implicit object extend_String_Monoid extends Monoid#Static[String] with Implementation[String, Monoid] { + def unit = "" + def inject($this: String): Monoid { type This = String } = + new Monoid { + type This = String + def + (that: This): This = $this + that + } + } + + def impl[From, To](implicit ev: Implementation[From, To]): Implementation[From, To] = + ev + + def sum[T](xs: List[T])(implicit $ev: Implementation[T, Monoid]) = { + //val ev = impl[T, Monoid] + ($ev.statics.unit /: xs)((x, y) => $ev.inject(x) + y) + } + } + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extend List[type T : Ord] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + static def pure[A]: This[A] + def map[B](f: A => B): ThisC[B] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.objects[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + object type ThisC[A] <: Monad[A] + + def flatMap[B](f: A => ThisC[B]): ThisC[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + + extend List[type T] : Monad[T] { + object type ThisC[A] = List[A] + object def pure[A] = Nil + + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + + extend (type T[X]: Monad[X])[T[type A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + object type ThisC[A] <: MonoIterable[A] + object def empty: This[A] + object def apply(xs: A*): This[A] + + def filter(p: A => Boolean): This[A] + } + + trait Iterable[A] extends MonoIterable[A] { + object type ThisC[A] <: Iterable[A] + + def map[B](f: A => B): ThisC[B] + def flatMap[B](f: A => ThisC[B]): ThisC[B] + } + + extend String : MonoIterable[Char] { + object type ThisC[A] = String + object def empty = "" + object def apply(xs: A*) = xs.mkString + + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + + extend String : Iterable[Char] { + object type ThisC[A] = IndexedSeq[A] + + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + + extend List[type T] : Iterable[T] { + object type ThisC[A] = List[A] + object def empty = Nil + object def apply(xs: A*) = (xs /: Nil)(_ :: _) + + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyExtensions3.scala b/tests/pending/pos/blueSkyExtensions3.scala new file mode 100644 index 000000000000..3870f785baec --- /dev/null +++ b/tests/pending/pos/blueSkyExtensions3.scala @@ -0,0 +1,160 @@ +/** A blue sky sketch how one might evolve extensions to do type classes + * without the current gap between functional and OO patterns. + * Largely inspired by the way Rust does it. + * + * The main additions (to be worked out in detail) is a self type `This` and + * a mechanism that a trait can abstract over companions of classes that implement it. + * Companion types and methods are declared using `static` for now, just to + * pick some familiar notation. + * + * Ideas about `This` (still vague at the moment) + * + * - Treat it as an additional abstract type in a trait, prefixed by the name of the trait + * - An implementing (non-trait) class binds the `This` types of all the traits it implements + * to itself. + * - Later subclasses do not re-bind `This` types already bound by their superclasses. + * (so in that sense, `This`-binding is like trait parameterization, the first implementing + * classes determines the self type and the parameters of a trait) + * - Paramerized classes have parameterized `This` types (e.g. Functor below). + */ +import Predef.{any2stringadd => _, _} +object blueSkyExtensions { + +// Semigroup and Monoid + + trait SemiGroup { + def + (that: This): This + } + + trait Monoid extends SemiGroup + object { + def unit: This + } + + extend Int : Monoid { + def + (that: Int) = this + that + } + object { + static def unit = 0 + } + + class C { + static Foo + static Bar + } + object Foo extends FooStatic BarStatic BazStatic + + extend String : Monoid { + def + (that: Int) = this ++ that + } + object { + def unit = "" + } + +// Ord + + trait Ord { + def compareTo(that: This): Int + def < (that: This) = compareTo < 0 + def > (that: This) = compareTo > 0 + } + + extend List[type T : Ord] : Ord { + def compareTo(that: List[T]): Int = (this, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + } + } + +// Functor and Monad + + trait Functor[A] { + def map[B](f: A => B): ThisC[B] + } + object { + type ThisC[A] <: Functor[A] + def pure[A]: ThisC[A] + } + + // Generically, `pure[A]{.map(f)}^n` + def develop[A, F[X] : Functor[X]](n: Int, f: A => A): F[A] = + if (n == 0) Functor.companion[F].pure[A] + else develop[A, F](n - 1, f).map(f) + + trait Monad[A] extends Functor[A] { + def flatMap[B](f: A => ThisC[B]): ThisC[B] + def map[B](f: A => B) = this.flatMap(f.andThen(pure)) + } + object { + type ThisC[A] <: Monad[A] + def pure[A]: ThisC[A] + } + + extend List[type T] : Monad[T] { + def flatMap[B](f: A => List[B]): List[B] = this match { + case x :: xs => f(x) ++ xs.flatMap(f) + case Nil => Nil + } + } + object { + type ThisC[A] = List[A] + def pure[A] = Nil + } + + extend (type T[X]: Monad[X])[T[type A]] { + def flatten: T[A] = this.flatMap(identity) + } + +// Iterables + + trait MonoIterable[A] { + def filter(p: A => Boolean): ThisC[A] + } + object { + type ThisC[A] <: MonoIterable[A] + def empty: ThisC[A] + def apply(xs: A*): ThisC[A] + } + + trait Iterable[A] extends MonoIterable[A] { + def map[B](f: A => B): ThisC[B] + def flatMap[B](f: A => ThisC[B]): ThisC[B] + } + object { + type ThisC[A] <: Iterable[A] + } + + extend String : MonoIterable[Char] { + def filter(p: Char => Boolean): String = ... + def map(f: Char => Char): String = ... + } + object { + type ThisC[A] = String + def empty = "" + def apply(xs: A*) = xs.mkString + } + + extend String : Iterable[Char] { + def map[B](f: Char => B): IndexedSeq[B] = ... + def flatMap[B](f: Char => IndexedSeq[B]): IndexedSeq[B] = ... + } + object { + type ThisC[A] = IndexedSeq[A] + } + + extend List[type T] : Iterable[T] { + def filter(p: T => Boolean): List[T] = ... + def map[B](f: T => B): List[B] = ... + def flatMap[B](f: T => List[B]): List[B] = ... + } + object { + type ThisC[A] = List[A] + def empty = Nil + def apply(xs: A*) = (xs /: Nil)(_ :: _) + } + +} \ No newline at end of file diff --git a/tests/pending/pos/blueSkyImplentations2.scala b/tests/pending/pos/blueSkyImplentations2.scala new file mode 100644 index 000000000000..de10c57db637 --- /dev/null +++ b/tests/pending/pos/blueSkyImplentations2.scala @@ -0,0 +1,66 @@ +object runtime { + + trait TypeClass { + type This + type StaticPart[This] + } + + trait Implementation[From] { + type This = From + type Implemented <: TypeClass + def inject(x: From): Implemented { type This = From } + } + + class CompanionOf[T] { type StaticPart[_] } + + def instance[From, To <: TypeClass]( + implicit ev1: Implementation[From] { type Implemented = To }, + ev2: CompanionOf[To]): Implementation[From] { type Implemented = To } & ev2.StaticPart[From] = + ev1.asInstanceOf + + implicit def inject[From](x: From)( + implicit ev1: Implementation[From]): ev1.Implemented { type This = From } = + ev1.inject(x) +} + +object semiGroups { + import runtime._ + + trait SemiGroup extends TypeClass { + def add (that: This): This + } + + trait Monoid extends SemiGroup { + type StaticPart[This] <: MonoidStatic[This] + } + abstract class MonoidStatic[This] { def unit: This } + + implicit def companionOfMonoid: CompanionOf[Monoid] { + type StaticPart[X] = MonoidStatic[X] + } = new CompanionOf[Monoid] { + type StaticPart[X] = MonoidStatic[X] + } + + implicit object extend_Int_Monoid extends MonoidStatic[Int] with Implementation[Int] { + type Implemented = Monoid + def unit: Int = 0 + def inject($this: Int) = new Monoid { + type This = Int + def add (that: This): This = $this + that + } + } + + implicit object extend_String_Monoid extends MonoidStatic[String] with Implementation[String] { + type Implemented = Monoid + def unit = "" + def inject($this: String): Monoid { type This = String } = + new Monoid { + type This = String + def add (that: This): This = $this ++ that + } + } + + def sum[T](xs: List[T])(implicit $ev: Implementation[T] { type Implemented = Monoid } ) = { + (instance[T, Monoid].unit /: xs)((x, y) => (x) add y) + } +} 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/escapes2.scala b/tests/pos/escapes2.scala index b94066936a57..bc10b58ac6d8 100644 --- a/tests/pos/escapes2.scala +++ b/tests/pos/escapes2.scala @@ -1,5 +1,5 @@ object Test { class C3[T](val elem: T) class D3[T](val elemD: T) extends C3[T](elemD) - def f[T](x: C3[T]) = x match { case d: D3[t] => d.elemD } + def f[T](x: C3[T]) = x match { case d: D3[type X] => d.elemD } } diff --git a/tests/pos/exhaust_2.scala b/tests/pos/exhaust_2.scala index 4f4e47c43b5e..3751c6ed756c 100644 --- a/tests/pos/exhaust_2.scala +++ b/tests/pos/exhaust_2.scala @@ -44,7 +44,7 @@ object ExhaustivityWarnBugReportMinimal { val v2: (Some[_], Int) = (???, ???) v2 match { - case (x: Some[t], _) => + case (x: Some[type t], _) => } val v3: (Option[_], FoundNode[_]) = (???, ???) diff --git a/tests/pos/existentials-harmful.scala b/tests/pos/existentials-harmful.scala index 91dbd4dfda34..23cb75aa6e4b 100644 --- a/tests/pos/existentials-harmful.scala +++ b/tests/pos/existentials-harmful.scala @@ -47,7 +47,7 @@ object ExistentialsConsideredHarmful { // Type annotation on bc is required ... possible compiler bug? // val bc : BoxCarrier[_ <: Animal] = aBox match { val bc = aBox match { - case tb : TransportBox[a] => new BoxCarrier(tb) { + case tb : TransportBox[type A] => new BoxCarrier(tb) { def speed: Int = 12 } } diff --git a/tests/pos/gadt-eval.scala b/tests/pos/gadt-eval.scala index 2cebba3f378d..fd2305abd1c2 100644 --- a/tests/pos/gadt-eval.scala +++ b/tests/pos/gadt-eval.scala @@ -13,7 +13,7 @@ object Test { def eval2[T](e: Exp[T]): T = e match { case e: Lit => e.value - case e: Pair[t1, t2] => + case e: Pair[type T1, type T2] => (eval(e.fst), eval(e.snd)) } } diff --git a/tests/pos/i0290-type-bind.scala b/tests/pos/i0290-type-bind.scala index 83fdbbcc54d4..26a5330ba47d 100644 --- a/tests/pos/i0290-type-bind.scala +++ b/tests/pos/i0290-type-bind.scala @@ -3,6 +3,9 @@ object foo{ x match { case t: List[tt] => t.head.asInstanceOf[tt] } + x match { + case t: List[type tt] => t.head.asInstanceOf[tt] + } } object bar { @@ -12,8 +15,8 @@ object bar { val x: AnyRef = new C x match { - case x: C[u] => - def x: u = x + case x: C[type U] => + def x: U = x val s: Seq[_] = x } } diff --git a/tests/pos/i0306.scala b/tests/pos/i0306.scala index 5a242fa83d91..dc38593c11dd 100644 --- a/tests/pos/i0306.scala +++ b/tests/pos/i0306.scala @@ -5,8 +5,8 @@ object bar { val x: AnyRef = new C val y = x match { - case x: C[u] => - def xx: u = xx + case x: C[type U] => + def xx: U = xx xx } diff --git a/tests/pos/i1365.scala b/tests/pos/i1365.scala index e7d47da4b759..31d6c370aeb4 100644 --- a/tests/pos/i1365.scala +++ b/tests/pos/i1365.scala @@ -10,4 +10,7 @@ class Test[A] { def g(cmd: Message[A]): Unit = cmd match { case s: Script[z] => s.iterator.foreach(x => g(x)) } + def h(cmd: Message[A]): Unit = cmd match { + case s: Script[type Z] => s.iterator.foreach(x => g(x)) + } } diff --git a/tests/pos/i947.scala b/tests/pos/i947.scala index 0f2d9e77583a..f6a4dd686051 100644 --- a/tests/pos/i947.scala +++ b/tests/pos/i947.scala @@ -6,8 +6,8 @@ object Test { override def equals(other: Any) = other match { case o: c => x == o.x - case xs: List[c] => false - case ys: List[d18383] => false + case xs: List[type C] => false + case ys: List[type d18383] => false case _ => false } diff --git a/tests/pos/opaque-augment.scala b/tests/pos/opaque-augment.scala new file mode 100644 index 000000000000..f9eaee4ecd30 --- /dev/null +++ b/tests/pos/opaque-augment.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + extend Logarithm { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(this) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(this) + math.exp(that)) + 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-digits.scala b/tests/pos/opaque-digits.scala new file mode 100644 index 000000000000..29eed387dc2e --- /dev/null +++ b/tests/pos/opaque-digits.scala @@ -0,0 +1,22 @@ +object pkg { + + import Character.{isAlphabetic, isDigit} + + class Alphabetic private[pkg] (val value: String) extends AnyVal + + object Alphabetic { + def fromString(s: String): Option[Alphabetic] = + if (s.forall(isAlphabetic(_))) Some(new Alphabetic(s)) + else None + } + + opaque type Digits = String + + object Digits { + def fromString(s: String): Option[Digits] = + if (s.forall(isDigit(_))) Some(s) + else None + + def asString(d: Digits): String = d + } +} \ No newline at end of file diff --git a/tests/pos/opaque-groups.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/t4070.scala b/tests/pos/t4070.scala index a9777f02ed20..2dcb8c1a6061 100644 --- a/tests/pos/t4070.scala +++ b/tests/pos/t4070.scala @@ -1,7 +1,7 @@ package a { // method before classes trait Foo { - def crash(x: Dingus[_]): Unit = x match { case m: Bippy[tv] => () } + def crash(x: Dingus[_]): Unit = x match { case m: Bippy[type tv] => () } class Dingus[T] class Bippy[CC[X] <: Seq[X]]() extends Dingus[CC[Int]] @@ -14,7 +14,7 @@ package b { class Dingus[T] class Bippy[CC[X] <: Seq[X]]() extends Dingus[CC[Int]] - def crash(x: Dingus[_]): Unit = x match { case m: Bippy[tv] => () } + def crash(x: Dingus[_]): Unit = x match { case m: Bippy[type tv] => () } } } diff --git a/tests/pos/t4579.scala b/tests/pos/t4579.scala index 907593e5bd73..54dff6f47b4a 100644 --- a/tests/pos/t4579.scala +++ b/tests/pos/t4579.scala @@ -111,7 +111,7 @@ object LispCaseClasses extends Lisp { def lookup(n: String): Data = if (n == name) expr(this) else Environment.this.lookup(n); } - def extend(name: String, v: Data) = extendRec(name, (env1 => v)); + def `extend`(name: String, v: Data) = extendRec(name, (env1 => v)); } val EmptyEnvironment = new Environment { def lookup(n: String): Data = lispError("undefined: " + n); @@ -165,7 +165,7 @@ object LispCaseClasses extends Lisp { case CONS(SYM("def"), CONS(SYM(name), CONS(y, CONS(z, NIL())))) => eval(z, env.extendRec(name, (env1 => eval(y, env1)))) case CONS(SYM("val"), CONS(SYM(name), CONS(y, CONS(z, NIL())))) => - eval(z, env.extend(name, eval(y, env))) + eval(z, env.`extend`(name, eval(y, env))) case CONS(SYM("lambda"), CONS(params, CONS(y, NIL()))) => mkLambda(params, y, env) case CONS(SYM("if"), CONS(c, CONS(t, CONS(e, NIL())))) => @@ -194,7 +194,7 @@ object LispCaseClasses extends Lisp { case (List(), List()) => env case (p :: ps1, arg :: args1) => - extendEnv(env.extend(p, arg), ps1, args1) + extendEnv(env.`extend`(p, arg), ps1, args1) case _ => lispError("wrong number of arguments") } @@ -208,26 +208,26 @@ object LispCaseClasses extends Lisp { } val globalEnv = EmptyEnvironment - .extend("=", FUN({ + .`extend`("=", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(if (arg1 == arg2) 1 else 0) case List(STR(arg1),STR(arg2)) => NUM(if (arg1 == arg2) 1 else 0)})) - .extend("+", FUN({ + .`extend`("+", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 + arg2) case List(STR(arg1),STR(arg2)) => STR(arg1 + arg2)})) - .extend("-", FUN({ + .`extend`("-", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 - arg2)})) - .extend("*", FUN({ + .`extend`("*", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 * arg2)})) - .extend("/", FUN({ + .`extend`("/", FUN({ case List(NUM(arg1),NUM(arg2)) => NUM(arg1 / arg2)})) - .extend("car", FUN({ + .`extend`("car", FUN({ case List(CONS(x, xs)) => x})) - .extend("cdr", FUN({ + .`extend`("cdr", FUN({ case List(CONS(x, xs)) => xs})) - .extend("null?", FUN({ + .`extend`("null?", FUN({ case List(NIL()) => NUM(1) case _ => NUM(0)})) - .extend("cons", FUN({ + .`extend`("cons", FUN({ case List(x, y) => CONS(x, y)})); def evaluate(x: Data): Data = eval(normalize(x), globalEnv); @@ -279,7 +279,7 @@ object LispAny extends Lisp { def lookup(n: String): Data = if (n == name) expr(this) else Environment.this.lookup(n); } - def extend(name: String, v: Data) = extendRec(name, (env1 => v)); + def `extend`(name: String, v: Data) = extendRec(name, (env1 => v)); } val EmptyEnvironment = new Environment { def lookup(n: String): Data = lispError("undefined: " + n); @@ -345,7 +345,7 @@ object LispAny extends Lisp { case 'def :: Symbol(name) :: y :: z :: Nil => eval(z, env.extendRec(name, (env1 => eval(y, env1)))) case 'val :: Symbol(name) :: y :: z :: Nil => - eval(z, env.extend(name, eval(y, env))) + eval(z, env.`extend`(name, eval(y, env))) case 'lambda :: params :: y :: Nil => mkLambda(params, y, env) case 'if :: c :: y :: z :: Nil => @@ -385,7 +385,7 @@ object LispAny extends Lisp { case (List(), List()) => env case (p :: ps1, arg :: args1) => - extendEnv(env.extend(p, arg), ps1, args1) + extendEnv(env.`extend`(p, arg), ps1, args1) case _ => lispError("wrong number of arguments") } @@ -399,25 +399,25 @@ object LispAny extends Lisp { } val globalEnv = EmptyEnvironment - .extend("=", Lambda{ + .`extend`("=", Lambda{ case List(arg1, arg2) => if (arg1 == arg2) 1 else 0}) - .extend("+", Lambda{ + .`extend`("+", Lambda{ case List(arg1: Int, arg2: Int) => arg1 + arg2 case List(arg1: String, arg2: String) => arg1 + arg2}) - .extend("-", Lambda{ + .`extend`("-", Lambda{ case List(arg1: Int, arg2: Int) => arg1 - arg2}) - .extend("*", Lambda{ + .`extend`("*", Lambda{ case List(arg1: Int, arg2: Int) => arg1 * arg2}) - .extend("/", Lambda{ + .`extend`("/", Lambda{ case List(arg1: Int, arg2: Int) => arg1 / arg2}) - .extend("nil", Nil) - .extend("cons", Lambda{ + .`extend`("nil", Nil) + .`extend`("cons", Lambda{ case List(arg1, arg2) => arg1 :: asList(arg2)}) - .extend("car", Lambda{ + .`extend`("car", Lambda{ case List(x :: xs) => x}) - .extend("cdr", Lambda{ + .`extend`("cdr", Lambda{ case List(x :: xs) => xs}) - .extend("null?", Lambda{ + .`extend`("null?", Lambda{ case List(Nil) => 1 case _ => 0}); diff --git a/tests/pos/t6205.scala b/tests/pos/t6205.scala index 52078bd5f46f..4246953ff939 100644 --- a/tests/pos/t6205.scala +++ b/tests/pos/t6205.scala @@ -2,8 +2,8 @@ class A[T] class Test1 { def x(backing: Map[A[_], Any]) = - for( (k: A[kt], v) <- backing) - yield (k: A[kt]) + for( (k: A[type KT], v) <- backing) + yield (k: A[KT]) } // this tests same thing as above, but independent of library classes, @@ -13,6 +13,6 @@ class Mapped[A] { def map[T](f: Holder[A] => T): Iterable[T] = ??? } class Test2 { def works(backing: Mapped[A[_]]): Iterable[A[_]] = backing.map(x => - x match {case Holder(k: A[kt]) => (k: A[kt])} + x match {case Holder(k: A[type KT]) => (k: A[KT])} ) } diff --git a/tests/pos/t6275.scala b/tests/pos/t6275.scala index 6b5ec7dcebfb..2ece602a1c0b 100644 --- a/tests/pos/t6275.scala +++ b/tests/pos/t6275.scala @@ -5,7 +5,7 @@ final class B[T] extends A[T] object ParsedAxis { type BI = B[Int] - def f1(a: A[Int]) = a match { case b: B[Int] => 3 } + def f1(a: A[Int]) = a match { case b: B[type Int] => 3 } def f2(a: A[Int]) = a match { case b: BI => 3 } def f3(a: A[Int]) = a match { case b: B[t] => 3 } } diff --git a/tests/pos/t8023.scala b/tests/pos/t8023.scala index 66d478abd51e..fcfa0bba210d 100644 --- a/tests/pos/t8023.scala +++ b/tests/pos/t8023.scala @@ -3,7 +3,7 @@ class D[K] object Test3 { def foo = (null: Any) match { - case a: C[k] => new C[k]() // this one worked before as the info of `A` was complete + case a: C[type K] => new C[K]() // this one worked before as the info of `A` was complete // () } } diff --git a/tests/pos/t946.scala b/tests/pos/t946.scala index c4bd6e9ba415..d256ce6b3abe 100644 --- a/tests/pos/t946.scala +++ b/tests/pos/t946.scala @@ -3,6 +3,6 @@ object pmbugbounds { class Foo[t <: Bar] {} (new Foo[Bar]) match { - case _ : Foo[x] => null + case _ : Foo[type X] => null } } 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/this-types.scala b/tests/pos/this-types.scala new file mode 100644 index 000000000000..d75de54b3ffa --- /dev/null +++ b/tests/pos/this-types.scala @@ -0,0 +1,16 @@ +trait A { + type A_This <: A +} +trait B extends A { + type A_This = B_This + type B_This <: B +} +trait C extends A { + type A_This = C_This + type C_This <: C +} +trait D extends B with C { + type B_This = D_This + type C_This = D_This + type D_This <: D +} \ No newline at end of file diff --git a/tests/pos/typepats.scala b/tests/pos/typepats.scala new file mode 100644 index 000000000000..0b21547d9393 --- /dev/null +++ b/tests/pos/typepats.scala @@ -0,0 +1,21 @@ +object Test { + val xs: Any = List(1, 2, 3) + xs match { + case xs: List[type T] => + } + trait I[T] + class C[T] extends I[T] + val y: I[Int] = new C[Int] + y match { + case _: C[type T] => + val x: T = 3 + } + + import collection.immutable.SortedSet + val s: Set[Int] = SortedSet(1, 2, 3) + s match { + case _: SortedSet[type T] => + val x: T = 3 + case _ => + } +} \ No newline at end of file diff --git a/tests/run/augment.check b/tests/run/augment.check new file mode 100644 index 000000000000..748a6ca11e55 --- /dev/null +++ b/tests/run/augment.check @@ -0,0 +1,16 @@ +12.566370614359172 +(1,a) +true +false +true +false +true +false +true +false +2 +List(1, 2, 3) +3 +3 +false +true diff --git a/tests/run/extensions.scala b/tests/run/extensions.scala new file mode 100644 index 000000000000..39823b646264 --- /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) + + extend Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + extend Circle : HasArea { + def area = this.radius * this.radius * math.Pi + } + +// Generic extendations + + extend List[type T] { + def second = this.tail.head + } + +// Specific extendations + + extend List[Int] { + def maxx = (0 /: this)(_ `max` _) + } + + extend 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 + } + + extend Rectangle[type T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + + extend Rectangle[type T](implicit ev: Eql[T]) { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic extensions + + extend (type T) { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic extensions + + trait HasEql[T] { + def === (that: T): Boolean + } + + extend (type S : Eql) : HasEql[S] { + def === (that: S): Boolean = implicitly[Eql[S]].eql(this, that) + } + + extend (type T2)(implicit ev: Eql[T2]) { + def ==== (that: T2): Boolean = implicitly[Eql[T2]].eql(this, that) + } + + extend Rectangle[type T : Eql] : 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, extend_type_S_Eql_S__HasEql_S} + // Nested generic arguments + + extend List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + extend (type T : Eql, T) { + def isSame = this._1 === this._2 + def isSame2 = extend_type_S_Eql_S__HasEql_S(this._1) == this._2 + } + +} + +object docs { + extend Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) + } + } + + extend List[List[type T]] { + def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) + } + + extend Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 + } + + object PostConditions { + opaque type EnsureResult[T] = T + + private object EnsureResult { + def wrap[T](x: T): EnsureResult[T] = x + def unwrap[T](x: EnsureResult[T]): T = x + } + + def result[T](implicit er: EnsureResult[T]): T = EnsureResult.unwrap(er) + + extend (type T) { + def ensuring[U](f: implicit EnsureResult[T] => Boolean): T = { + assert(f(EnsureResult.wrap(this))) + this + } + } + } + import PostConditions._ + + val s = List(1, 2, 3).sum.ensuring(result == 6) + +} + +import 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(extend_type_T_Eql_T_T((3, 3)).isSame) +} \ No newline at end of file diff --git a/tests/run/magnet.scala b/tests/run/magnet.scala new file mode 100644 index 000000000000..f8ba44cb23c2 --- /dev/null +++ b/tests/run/magnet.scala @@ -0,0 +1,46 @@ +import scala.concurrent.Future +object magnets { + + enum StatusCode { + case OK, Error + } + + trait Marshaller[T] { def marshall(t: T): String } + class HttpResponse + + sealed trait CompletionMagnet { + type Result + def apply(): Result + } + + object CompletionMagnet { + + extend (StatusCode, type T: Marshaller) : CompletionMagnet { + type Result = String + def apply(): String = implicitly[Marshaller[T]].marshall(this._2) + } + + extend Future[HttpResponse] : CompletionMagnet { + type Result = Int + def apply(): Int = 1 + } + + extend Future[StatusCode] : CompletionMagnet { + type Result = Int + def apply(): Int = 2 + } + } + + implicit object stringMarshaller extends Marshaller[String] { + def marshall(x: String): String = x + } + + def complete(magnet: CompletionMagnet): magnet.Result = magnet() +} + +object Test extends App { + import magnets._ + + assert(complete((StatusCode.OK, "hello")) == "hello") + +} \ No newline at end of file