diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 30dad2f44847..f488530e408b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -6,11 +6,12 @@ import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._, transform.SymUtils._ -import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} +import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, AugmentName} import language.higherKinds import typer.FrontEnd import collection.mutable.ListBuffer import util.Property +import config.Printers.desugr import reporting.diagnostic.messages._ import reporting.trace @@ -154,6 +155,25 @@ object desugar { ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit) } + private def desugarTypeBindings( + bindings: List[TypeDef], + forPrimaryConstructor: Boolean = false)(implicit ctx: Context): (List[TypeDef], List[ValDef]) = { + val epbuf = new ListBuffer[ValDef] + def desugarContextBounds(rhs: Tree): Tree = rhs match { + case ContextBounds(tbounds, cxbounds) => + epbuf ++= makeImplicitParameters(cxbounds, forPrimaryConstructor) + tbounds + case LambdaTypeTree(tparams, body) => + cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) + case _ => + rhs + } + val bindings1 = bindings mapConserve { tparam => + cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) + } + (bindings1, epbuf.toList) + } + /** Expand context bounds to evidence params. E.g., * * def f[T >: L <: H : B](params) @@ -171,21 +191,8 @@ object desugar { private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = { val DefDef(name, tparams, vparamss, tpt, rhs) = meth val mods = meth.mods - val epbuf = new ListBuffer[ValDef] - def desugarContextBounds(rhs: Tree): Tree = rhs match { - case ContextBounds(tbounds, cxbounds) => - epbuf ++= makeImplicitParameters(cxbounds, isPrimaryConstructor) - tbounds - case LambdaTypeTree(tparams, body) => - cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body)) - case _ => - rhs - } - val tparams1 = tparams mapConserve { tparam => - cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) - } - - val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList) + val (tparams1, evidenceParams) = desugarTypeBindings(tparams, isPrimaryConstructor) + val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), evidenceParams) /** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */ def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { @@ -293,6 +300,7 @@ object desugar { def isAnyVal(tree: Tree): Boolean = tree match { case Ident(tpnme.AnyVal) => true case Select(qual, tpnme.AnyVal) => isScala(qual) + case TypedSplice(tree) => tree.tpe.isRef(defn.AnyValClass) case _ => false } def isScala(tree: Tree): Boolean = tree match { @@ -749,6 +757,124 @@ 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) + } + + /** augment [ @] extends { } } + * -> + * implicit class ($this: ) + * extends { } + * + * where + * + * = , if there is a ` @` binding + * = unqiue, expanded name relative to top-level class of , otherwise + * = "_augment__to_" if is nonempty + * = "_augment_" otherwise + * = underlying type name of , or "" + * = underlying type name of first extended parent, or "" + * + * (, ) = decomposeTypePattern() + * (, ) = desugarTypeBindings() + * = concatenated with in one clause + * = with each occurrence of unqualified `this` substituted by `$this`. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * augment [ @] { } + * -> + * implicit class ($this: ) + * extends AnyVal { } + * + * where + * + * = where each method definition gets as last parameter section. + * , are as above. + */ + def augmentation(tree: Augment)(implicit ctx: Context): Tree = { + val Augment(id, augmented, impl) = tree + val isSimpleExtension = + impl.parents.isEmpty && + impl.self.isEmpty && + impl.body.forall(_.isInstanceOf[DefDef]) + val (decorated, bindings) = decomposeTypePattern(augmented) + val (typeParams, evidenceParams) = + desugarTypeBindings(bindings, forPrimaryConstructor = !isSimpleExtension) + val decoName = id match { + case Ident(name) => + name.asTypeName + case EmptyTree => + def clsName(tree: Tree): String = leadingName("", tree) + val fromName = clsName(augmented) + val toName = impl.parents match { + case parent :: _ if !clsName(parent).isEmpty => "_to_" + clsName(parent) + case _ => "" + } + val core = s"${str.AUGMENT}$fromName$toName".toTermName + AugmentName.fresh(core.expandedName(ctx.owner.topLevelClass)).toTypeName + } + + val firstParam = ValDef(nme.SELF, decorated, EmptyTree).withFlags(Private | Local | ParamAccessor) + var constr1 = + cpy.DefDef(impl.constr)( + tparams = typeParams.map(_.withFlags(Param | Private | Local)), + vparamss = (firstParam :: Nil) :: impl.constr.vparamss) + var parents1 = impl.parents + var body1 = substThis.transform(impl.body) + if (isSimpleExtension) { + constr1 = cpy.DefDef(constr1)(vparamss = constr1.vparamss.take(1)) + parents1 = ref(defn.AnyValType) :: Nil + body1 = body1.map { + case ddef: DefDef => + def resetFlags(vdef: ValDef) = + vdef.withMods(vdef.mods &~ PrivateLocalParamAccessor | Param) + val originalParams = impl.constr.vparamss.headOption.getOrElse(Nil).map(resetFlags) + addEvidenceParams(addEvidenceParams(ddef, originalParams), evidenceParams) + } + } + else + constr1 = addEvidenceParams(constr1, evidenceParams) + + val icls = + TypeDef(decoName, + cpy.Template(impl)(constr = constr1, parents = parents1, body = body1)) + .withFlags(Implicit) + desugr.println(i"desugar $augmented --> $icls") + classDef(icls) + } + + private val substThis = new UntypedTreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case This(Ident(tpnme.EMPTY)) => Ident(nme.SELF).withPos(tree.pos) + case _ => super.transform(tree) + } + } + + private val leadingName = new UntypedTreeAccumulator[String] { + override def apply(x: String, tree: Tree)(implicit ctx: Context): String = + if (x.isEmpty) + tree match { + case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre) + case tree: RefTree if tree.name.isTypeName => tree.name.toString + case tree: TypeDef => tree.name.toString + case tree: Tuple => "Tuple" + case tree: Function => "Function" + case _ => foldOver(x, tree) + } + else x + } + def defTree(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: ValDef => valDef(tree) case tree: TypeDef => if (tree.isClassDef) classDef(tree) else tree @@ -757,6 +883,7 @@ object desugar { else defDef(tree) case tree: ModuleDef => moduleDef(tree) case tree: PatDef => patDef(tree) + case tree: Augment => augmentation(tree) } /** { stats; } diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 252b301c2954..6bf80f274804 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -40,6 +40,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } + /** augment id @ augmented impl */ + case class Augment(id: Tree, augmented: Tree, impl: Template) extends DefTree + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree 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) @@ -409,6 +414,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: ModuleDef if (name eq tree.name) && (impl eq tree.impl) => tree case _ => finalize(tree, untpd.ModuleDef(name, impl)) } + def Augment(tree: Tree)(id: Tree, augmented: Tree, impl: Template) = tree match { + case tree: Augment if (id eq tree.id) && (augmented eq tree.augmented) && (impl eq tree.impl) => tree + case _ => finalize(tree, untpd.Augment(id, augmented, impl)) + } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry 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 dc9e700cefc4..00063b55789f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -530,6 +530,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") @@ -1157,6 +1158,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/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index d39ca3f6a5c6..64764bfcc3d8 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -287,6 +287,7 @@ object NameKinds { val SkolemName = new UniqueNameKind("?") val LiftedTreeName = new UniqueNameKind("liftedTree") val SuperArgName = new UniqueNameKind("$superArg$") + val AugmentName = new UniqueNameKind("_") /** A kind of unique extension methods; Unlike other unique names, these can be * unmangled. diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 1e64ff94d872..008f22b28d77 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -28,6 +28,7 @@ object StdNames { final val LOCALDUMMY_PREFIX = " 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 } @@ -1800,6 +1847,16 @@ object SymDenotations { .copyCaches(this, phase.next) .installAfter(phase) } + + private[this] var myCompanion: Symbol = NoSymbol + + /** Register companion class */ + override def registerCompanion(companion: Symbol)(implicit ctx: Context) = + if (companion.canHaveCompanion && !unforcedIsAbsent && !companion.unforcedIsAbsent) + myCompanion = companion + + override def registeredCompanion(implicit ctx: Context) = { ensureCompleted(); myCompanion } + override def registeredCompanion_=(c: Symbol) = { myCompanion = c } } /** The denotation of a package class. diff --git a/compiler/src/dotty/tools/dotc/core/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 989a6ae7e60c..ce63d751e43f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -759,9 +759,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { * tp1 <:< tp2 using fourthTry (this might instantiate params in tp1) * tp1 <:< app2 using isSubType (this might instantiate params in tp2) */ - def compareLower(tycon2bounds: TypeBounds, tyconIsTypeRef: Boolean): Boolean = + def compareLower(tycon2bounds: TypeBounds, followSuperType: Boolean): Boolean = if (tycon2bounds.lo eq tycon2bounds.hi) - if (tyconIsTypeRef) recur(tp1, tp2.superType) + if (followSuperType) recur(tp1, tp2.superType) else isSubApproxHi(tp1, tycon2bounds.lo.applyIfParameterized(args2)) else fallback(tycon2bounds.lo) @@ -770,12 +770,14 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case param2: TypeParamRef => isMatchingApply(tp1) || canConstrain(param2) && canInstantiate(param2) || - compareLower(bounds(param2), tyconIsTypeRef = false) + compareLower(bounds(param2), followSuperType = false) case tycon2: TypeRef => isMatchingApply(tp1) || { tycon2.info match { case info2: TypeBounds => - compareLower(info2, tyconIsTypeRef = true) + val gbounds2 = ctx.gadt.bounds(tycon2.symbol) + if (gbounds2 == null) compareLower(info2, followSuperType = true) + else compareLower(gbounds2 & info2, followSuperType = false) case info2: ClassInfo => val base = tp1.baseType(info2.cls) if (base.exists && base.ne(tp1)) @@ -807,8 +809,12 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } canConstrain(param1) && canInstantiate || isSubType(bounds(param1).hi.applyIfParameterized(args1), tp2, approx.addLow) - case tycon1: TypeRef if tycon1.symbol.isClass => - false + case tycon1: TypeRef => + !tycon1.symbol.isClass && { + val gbounds1 = ctx.gadt.bounds(tycon1.symbol) + if (gbounds1 == null) recur(tp1.superType, tp2) + else recur((gbounds1.hi & tycon1.info.bounds.hi).applyIfParameterized(args1), tp2) + } case tycon1: TypeProxy => recur(tp1.superType, tp2) case _ => diff --git a/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 3675d6e8033c..bf0a8eaa4e6f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2154,7 +2154,7 @@ object Types { if (ctx.erasedTypes) tref else cls.info match { case cinfo: ClassInfo => cinfo.selfType - case cinfo: ErrorType if ctx.mode.is(Mode.Interactive) => cinfo + case _: ErrorType | NoType if ctx.mode.is(Mode.Interactive) => cls.info // can happen in IDE if `cls` is stale } diff --git a/compiler/src/dotty/tools/dotc/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..fef25c11fbc4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -183,6 +183,7 @@ Standard-Section: "ASTs" TopLevelStat* OVERRIDE INLINE // inline method MACRO // inline method containing toplevel splices + OPAQUE // opaque type STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait @@ -299,6 +300,7 @@ object TastyFormat { final val DEFAULTparameterized = 30 final val STABLE = 31 final val MACRO = 32 + final val OPAQUE = 33 // Cat. 2: tag Nat @@ -410,7 +412,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= MACRO || + firstSimpleTreeTag <= tag && tag <= OPAQUE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -432,6 +434,7 @@ object TastyFormat { | OVERRIDE | INLINE | MACRO + | OPAQUE | STATIC | OBJECT | TRAIT @@ -486,6 +489,7 @@ object TastyFormat { case OVERRIDE => "OVERRIDE" case INLINE => "INLINE" case MACRO => "MACRO" + case OPAQUE => "OPAQUE" case STATIC => "STATIC" case OBJECT => "OBJECT" case TRAIT => "TRAIT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index f9652f19a4d8..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..bbdf9c0bede1 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, ofAugmentation: 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 (!ofAugmentation && in.token == RPAREN) Nil else { def funArgMods(): Unit = { if (in.token == IMPLICIT) { @@ -1890,7 +1924,8 @@ object Parsers { } } funArgMods() - + if (ofAugmentation && !imods.is(Implicit)) + syntaxError(i"parameters of augment clause 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) || ofAugmentation) 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,34 @@ object Parsers { Template(constr, parents, EmptyValDef, Nil) } + /** Augmentation ::= ‘augment’ [id @] BindingTypePattern + * [[nl] ImplicitParamClause] Additions + * BindingTypePattern ::= AnnotType + * Additions ::= ‘extends’ Template + * | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + */ + def augmentation(): Augment = atPos(in.skipToken(), nameStart) { + var id: Tree = EmptyTree + if (isIdent && lookaheadIn(AT)) { + id = typeIdent() + in.nextToken() + } + val augmented = withinTypePattern(binding = true)(annotType()) + val vparamss = paramClauses(tpnme.EMPTY, ofAugmentation = true).take(1) + val constr = makeConstructor(Nil, vparamss) + val isSimpleExtension = in.token != EXTENDS + val templ = templateClauseOpt(constr, bodyRequired = true) + if (isSimpleExtension) { + def checkDef(tree: Tree) = tree match { + case _: DefDef | EmptyValDef => // ok + case _ => syntaxError("`def` expected", tree.pos.startPos) + } + checkDef(templ.self) + templ.body.foreach(checkDef) + } + Augment(id, augmented, templ) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2284,26 +2347,27 @@ object Parsers { * @return a pair consisting of the template, and a boolean which indicates * whether the template misses a body (i.e. no {...} part). */ - def template(constr: DefDef, isEnum: Boolean = false): (Template, Boolean) = { + def template(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): (Template, Boolean) = { newLineOptWhenFollowedBy(LBRACE) if (in.token == LBRACE) (templateBodyOpt(constr, Nil, isEnum), false) else { val parents = tokenSeparated(WITH, constrApp) newLineOptWhenFollowedBy(LBRACE) - if (isEnum && in.token != LBRACE) + if (bodyRequired && in.token != LBRACE) syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) val missingBody = in.token != LBRACE (templateBodyOpt(constr, parents, isEnum), missingBody) } } - /** TemplateOpt = [`extends' Template | TemplateBody] + /** TemplateClause = `extends' Template | TemplateBody + * TemplateClauseOpt = [TemplateClause] */ - def templateOpt(constr: DefDef, isEnum: Boolean = false): Template = - if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum)._1 } + def templateClauseOpt(constr: DefDef, isEnum: Boolean = false, bodyRequired: Boolean = false): Template = + if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum, bodyRequired)._1 } else { newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) template(constr, isEnum)._1 + if (in.token == LBRACE || bodyRequired) template(constr, isEnum, bodyRequired)._1 else Template(constr, Nil, EmptyValDef, Nil) } @@ -2379,6 +2443,7 @@ object Parsers { * TemplateStat ::= Import * | Annotations Modifiers Def * | Annotations Modifiers Dcl + * | Augmentation * | Expr1 * | * EnumStat ::= TemplateStat @@ -2409,6 +2474,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == AUGMENT) + stats += augmentation() else if (isExprIntro) stats += expr1() else if (isDefIntro(modifierTokensOrCase)) @@ -2453,6 +2520,7 @@ object Parsers { * BlockStat ::= Import * | Annotations [implicit] [lazy] Def * | Annotations LocalModifiers TmplDef + * | Augmentation * | Expr1 * | */ @@ -2463,6 +2531,8 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == AUGMENT) + stats += augmentation() else if (isExprIntro) stats += expr(Location.InBlock) else if (isDefIntro(localModifierTokens)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 0cc0cc16fcea..426e4ffa8f11 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 AUGMENT = 66; enter(AUGMENT, "augment") /** 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, AUGMENT) 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, AUGMENT) 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 719b9498671f..da8ca5138e3c 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.{AugmentName, 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,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case info: ImportType => return s"import $info.expr.show" case _ => } + sym.name.toTermName match { + case AugmentName(ExpandedName(_, core), n) => + assert(core.startsWith(str.AUGMENT)) + return Str(s"augmentation ${core.drop(str.AUGMENT.length)}") ~ (Str(s"/$n") provided n > 1) + case _ => + } + if (sym.is(ModuleClass)) kindString(sym) ~~ (nameString(sym.name.stripModuleClassSuffix) + idString(sym)) else diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 312e2d4093db..9106772c0801 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -212,8 +212,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // Synthetic methods that are always present do not affect the API // and can therefore be ignored. - def alwaysPresent(s: Symbol) = - s.isCompanionMethod || (csym.is(ModuleClass) && s.isConstructor) + def alwaysPresent(s: Symbol) = csym.is(ModuleClass) && s.isConstructor val decls = cinfo.decls.filter(!alwaysPresent(_)) val apiDecls = apiDefinitions(decls) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index c0917ac8fb2a..c32a77ea1f0f 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 = { @@ -109,10 +121,13 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } def registerCompanion(name: TermName, forClass: Symbol): TermSymbol = { - val (modul, mcCompanion, classCompanion) = newCompanion(name, forClass) + val modul = newCompanion(name, forClass) if (ctx.owner.isClass) modul.enteredAfter(thisPhase) - mcCompanion.enteredAfter(thisPhase) - classCompanion.enteredAfter(thisPhase) + val modcls = modul.moduleClass + modcls.registerCompanion(forClass) + val cls1 = forClass.copySymDenotation() + cls1.registerCompanion(modcls) + cls1.installAfter(thisPhase) modul } @@ -133,15 +148,9 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => addMissingCompanions(reorder(stats, Nil)) } - private def newCompanion(name: TermName, forClass: Symbol)(implicit ctx: Context) = { - val modul = ctx.newCompleteModuleSymbol(forClass.owner, name, Synthetic, Synthetic, + private def newCompanion(name: TermName, forClass: Symbol)(implicit ctx: Context) = + ctx.newCompleteModuleSymbol(forClass.owner, name, Synthetic, Synthetic, defn.ObjectType :: Nil, Scopes.newScope, assocFile = forClass.asClass.assocFile) - val mc = modul.moduleClass - - val mcComp = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, forClass, mc) - val classComp = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, mc, forClass) - (modul, mcComp, classComp) - } /** elimiate self in Template */ override def transformTemplate(impl: Template)(implicit ctx: Context): Tree = { diff --git a/compiler/src/dotty/tools/dotc/transform/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 c30fa18a807e..934af71743b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -126,15 +126,6 @@ class SymUtils(val self: Symbol) extends AnyVal { self } - def registerCompanionMethod(name: Name, target: Symbol)(implicit ctx: Context) = { - if (!self.unforcedDecls.lookup(name).exists) { - val companionMethod = ctx.synthesizeCompanionMethod(name, target, self) - if (companionMethod.exists) { - companionMethod.entered - } - } - } - /** If this symbol is an enum value or a named class, register it as a child * in all direct parent classes which are sealed. * @param @late If true, register only inaccessible children (all others are already diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 9e0639a944f6..8239f84e6f9f 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -370,10 +370,7 @@ class TreeChecker extends Phase with SymTransformer { checkOwner(impl.constr) def isNonMagicalMethod(x: Symbol) = - x.is(Method) && - !x.isCompanionMethod && - !x.isValueClassConvertMethod && - !(x.is(Macro) && ctx.phase.refChecked) + x.is(Method) && !x.isValueClassConvertMethod && !(x.is(Macro) && ctx.phase.refChecked) val symbolsNotDefined = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMethod) -- impl.body.map(_.symbol) - constr.symbol diff --git a/compiler/src/dotty/tools/dotc/typer/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 7bf765c21ae1..090f5f101d7b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -398,7 +398,7 @@ trait ImplicitRunInfo { self: Run => override implicit protected val ctx: Context = liftingCtx override def stopAtStatic = true def apply(tp: Type) = tp match { - case tp: TypeRef if tp.symbol.isAbstractOrAliasType => + case tp: TypeRef if !tp.symbol.canHaveCompanion => val pre = tp.prefix def joinClass(tp: Type, cls: ClassSymbol) = AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner)) @@ -406,7 +406,7 @@ trait ImplicitRunInfo { self: Run => (lead /: tp.classSymbols)(joinClass) case tp: TypeVar => apply(tp.underlying) - case tp: AppliedType if !tp.tycon.typeSymbol.isClass => + case tp: AppliedType if !tp.tycon.typeSymbol.canHaveCompanion => def applyArg(arg: Type) = arg match { case TypeBounds(lo, hi) => AndType.make(lo, hi) case WildcardType(TypeBounds(lo, hi)) => AndType.make(lo, hi) @@ -444,21 +444,24 @@ trait ImplicitRunInfo { self: Run => case tp: NamedType => val pre = tp.prefix comps ++= iscopeRefs(pre) - def addClassScope(cls: ClassSymbol): Unit = { - def addRef(companion: TermRef): Unit = { - val compSym = companion.symbol - if (compSym is Package) - addRef(companion.select(nme.PACKAGE)) - else if (compSym.exists) - comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] - } - def addParentScope(parent: Type): Unit = - iscopeRefs(tp.baseType(parent.typeSymbol)) foreach addRef - val companion = cls.companionModule + def addRef(companion: TermRef): Unit = { + val compSym = companion.symbol + if (compSym is Package) + addRef(companion.select(nme.PACKAGE)) + else if (compSym.exists) + comps += companion.asSeenFrom(pre, compSym.owner).asInstanceOf[TermRef] + } + def addCompanionOf(sym: Symbol) = { + val companion = sym.companionModule if (companion.exists) addRef(companion.termRef) - cls.classParents foreach addParentScope } - tp.classSymbols(liftingCtx) foreach addClassScope + def addClassScope(cls: ClassSymbol): Unit = { + addCompanionOf(cls) + for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.typeSymbol))) + addRef(ref) + } + if (tp.widen.typeSymbol.is(Opaque)) addCompanionOf(tp.widen.typeSymbol) + else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 69836719c7d3..fe605981317f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -103,11 +103,19 @@ trait NamerContextOps { this: Context => /** A new context for the interior of a class */ def inClassContext(selfInfo: DotClass /* Should be Type | Symbol*/): Context = { - val localCtx: Context = ctx.fresh.setNewScope + var localCtx: FreshContext = ctx.fresh.setNewScope selfInfo match { case sym: Symbol if sym.exists && sym.name != nme.WILDCARD => localCtx.scope.openForMutations.enter(sym) case _ => } + if (ctx.owner.is(Module)) { + val opaq = ctx.owner.companionOpaqueType + val alias = opaq.opaqueAlias + if (alias.exists) { + localCtx = localCtx.setFreshGADTBounds + localCtx.gadt.setBounds(opaq, TypeAlias(alias)) + } + } localCtx } @@ -506,7 +514,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 +620,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 +846,7 @@ class Namer { typer: Typer => addInlineInfo(denot) denot.info = typeSig(sym) Checking.checkWellFormed(sym) + denot.normalizeOpaque() denot.info = avoidPrivateLeaks(sym, sym.pos) } } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 2e8aa21f3f55..0c3d4b5f66b3 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 + | Augmentation | 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 ‘:’ ‘_’ ‘*’ ‘)’ + +Augmentation ::= ‘augment’ [id ‘@’] BindingTypePattern + [[nl] ImplicitParamClause] AugmentClause Augment(name, templ) +AugmentClause ::= ‘extends’ 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 + | Augmentation | Expr1 | SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) diff --git a/docs/docs/reference/augments/method-augments.md b/docs/docs/reference/augments/method-augments.md new file mode 100644 index 000000000000..a7e128f4d395 --- /dev/null +++ b/docs/docs/reference/augments/method-augments.md @@ -0,0 +1,189 @@ +--- +layout: doc-page +title: "Method Augmentations" +--- + +Method augmentation is a way to define _extension methods_. Here is a simple example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +augment Circle { + def circumference: Double = this.radius * math.Pi * 2 +} +``` + +The `augment Circle` clause adds an extension method `circumference` to values of class `Circle`. Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Meaning of `this` + +Inside an extension method, the name `this` stands for the receiver on which the +method is applied when it is invoked. E.g. in the application of `circle.circumference`, +the `this` in the body of `circumference` refers to `circle`. Unlike for regular methods, +an explicit `this` is mandatory to refer to members of the receiver. So the following +gives a compilation error: + +```scala + | def circumference = radius * math.Pi * 2 + | ^^^^^^ + | not found: radius +``` + +### Scope of Augment Clauses + +Augment clauses can appear anywhere in a program; there is no need to co-define them with the types they augment. Extension methods are available wherever their defining augment clause is in scope. Augment clauses can be inherited or wildcard-imported like normal definitions. This is usually sufficient to control their visibility. If more control is desired, one can also attach a name to an augment clause, like this: + +```scala +package shapeOps + +augment circleOps @ Circle { + def circumference: Double = this.radius * math.Pi * 2 + def area: Double = this.radius * this.radius * math.Pi +} +``` +Labelled augments can be imported individually by their name: + +```scala +import shapeOps.circleOps // makes circumference and area available +``` + +### Augmented Types + +An augment clause may add methods to arbitrary types. For instance, the following +clause adds a `longestStrings` extension method to a `Seq[String]`: + +```scala +augment Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) + } +} +``` + +### Augmented Type Patterns + +The previous example augmented a specific instance of a generic type. It is also possible +to augment a generic type itself, using a _type pattern_: + +```scala +augment List[type T] { + def second: T = this.tail.head +} +``` + +The `type T` argument indicates that the augment applies to `List[T]`s for any type `T`. +We also say that `type T` introduces `T` as a _variable_ in the type pattern `List[type T]`. +Type variables may appear anywhere in a type pattern. Example: + +```scala +augment List[List[type T]] { + def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) +} +``` + +### Type Patterns in Cases + +The `type ...` syntax for pattern bound type variables also applies to patterns in +case clauses of `match` and `try` expressions. For instance: + +```scala +def f[T](s: Set[T], x: T) = s match { + case _: SortedSet[type U] => ... // binds `U`, infers that `U = T` + case _ => +} +``` + +Previously, one used a lower-case name to indicate a variable in a type pattern, as in: + +```scala + case _: SortedSet[u] => ... // binds `u`, infers that `u = T` +``` + +While being more regular wrt term variables in patterns, this usage is harder to read, and has the problem that it feels unnatrual to have to write type names in lower case. It will therefore be phased out to be replaced by the explicit `type T` syntax. + +Type patterns in cases only come in unbounded form; the bounds defined in the next section are not applicable to them. + +### Bounds in Augmented Type Patterns + +It is also possible to use bounds for the type variables in an augmented type pattern. Examples: + +```scala +augment List[type T <: Shape] { + def totalArea = this.map(_.area).sum +} +``` + +Context-bounds are also supported: + +```scala +augment Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +### Implicit Parameters for Type Patterns + +The standard translation of context bounds expands the bound in the last example to an implicit _evidence_ parameter of type `math.Ordering[T]`. It is also possible to give evidence parameters explicitly. The following example is equivalent to the previous one: + +```scala +augment Seq[type T](implicit ev: math.Ordering[T]) { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +There can be only one parameter clause following a type pattern and it must be implicit. As usual, one can combine context bounds and implicit evidence parameters. + +### Toplevel Type Variables + +A type pattern consisting of a top-level typevariable introduces a fully generic augmentation. For instance, the following augment introduces `x ~ y` as an alias +for `(x, y)`: + +```scala +augment (type T) { + def ~ [U](that: U) = (this, that) +} +``` + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and augments to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + augment (type T) { + def ensuring[U](condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(this) + assert(condition) + this + } + } +} +object Test { + import PostConditions._ + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument condition to `ensuring` such as +`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope +to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } diff --git a/docs/docs/reference/augments/trait-augments.md b/docs/docs/reference/augments/trait-augments.md new file mode 100644 index 000000000000..fed7cd6a5c50 --- /dev/null +++ b/docs/docs/reference/augments/trait-augments.md @@ -0,0 +1,76 @@ +--- +layout: doc-page +title: "Trait Augmentations" +--- + +In addition to adding methods, an augmentation can also implement traits and classes. For instance, + +```scala +trait HasArea { + def area: Double +} + +augment circleOps @ Circle extends HasArea { + def area = this.radius * this.radius * math.Pi +} +``` + +This augmentation makes `Circle` implement the `HasArea` trait. Specifically, it defines an implicit subclass of `HasArea` +which takes a `Circle` as argument and provides the given implementation. Hence, the implementation of the augmentation above would be like this + +```scala +implicit class circleOps($this: Circle) extends HasArea { + def area = $this.radius * $this.radius * math.Pi +} +``` + +A trait augmentation can thus provide a kind of "extends" relationship that can be defined independently of the types it connects. + +### Generic Trait Augmentations + +Just like method augmentations, trait augmentations can be generic with and their type parameters can have bounds. + +For example, assume we have the following two traits, which define binary and unary (infix) equality tests: + +```scala +trait Eql[T] { + def eql (x: T, y: T): Boolean +} + +trait HasEql[T] { + def === (that: T) +} +``` + +The following augment makes any type `T` with an implicit `Eql[T]` instance implement `HasEql`: + +```scala +augment (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) +} +``` + +### Syntax of Augmentations + +The syntax of augmentations iss pecified below as a delta with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) + + Augmentation ::= ‘augment’ [id ‘@’] BindingTypePattern + [[nl] ImplicitParamClause] AugmentClause + AugmentClause ::= ‘extends’ Template + | [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ + + ImplicitParamClause ::= [nl] ‘(’ ImplicitMods ClsParams ‘)’ + ImplicitMods ::= `implicit` [`ghost`] | `ghost` `implicit` + + BindingTypePattern: := AnnotType + Type ::= ... + | ‘type’ TypeParamCore (if inside a BindingTypePattern) + TypeParamCore ::= id [HkTypeParamClause] TypeParamBounds + +In this definition, type patterns and types share the same productions. However, the production + + Type ::= ‘type’ TypeParamCore + +is applicable only inside a `BindingTypePattern`. + + diff --git a/docs/docs/reference/augments/translation.md b/docs/docs/reference/augments/translation.md new file mode 100644 index 000000000000..ad5e6b6d9158 --- /dev/null +++ b/docs/docs/reference/augments/translation.md @@ -0,0 +1,93 @@ +--- +layout: doc-page +title: "Translation of Augmentations" +--- + +Augmentations are closely related to implicit classes and can be translated into them. In short, +a method augmentation translates into an implicit value class and a trait implementation translates +into a regular implicit class. The following sections sketch this translation. + +Conversely, it is conceivable (and desirable) to replace most usages of implicit classes and value classes by augmentations and [opaque types](../opaques.html). We plan to [drop](../dropped/implicit-value-classes.html) +these constructs in future versions of the language. Once that is achieved, the translations described +below can be simply composed with the existing translations of implicit and value classes into the core language. It is +not necessary to retain implicit and value classes as an intermediate step. + + +### Decomposing Type Patterns + +First, define a function `decompose` to decompose a type pattern `TP` into a list of type binders `tparams` and a type `T`. Going from left to right, every type binder `type U ` in `TP` is added to `tparams` and is replaced by the reference `U` in `T`. For instance, the type pattern + +```scala +Map[type K <: AnyRef, List[type T]] +``` +would be decomposed into the binders `type K <: AnyRef` and `type T` and the type `Map[K, List[T]]`. + +### Translation of Method Augmentations + +Now, a assume a method augmentation + + augment @ { } + +where ` @` or `` can be absent. For simplicity assume that there are no context bounds in +type definitions of ``. This is no essential restriction as any such context bounds can be rewritten in a prior step to be evidence paramseters in ``. Let `(, )` be the decomposition of ``. +The augment clause can be translated to the following implicit value class: + + implicit class ($this: ) extends AnyVal { } + +Here `` results from `` by augmenting any definition in with the parameters and +replacing any occurrence of `this` with `$this`. If the label ` @` is missing, a fresh compiler-generated name is chosen instead as the name of the implicit class. + +For example, the augmentation + +```scala +augment seqOps @ Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 +} +``` + +would be translated to: + +```scala +implicit class seqOps[T]($this: List[T]) extends AnyVal { + def indexOfLargest (implicit $ev: math.Ordering[T]) = $this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest(implicit $ev: math.Ordering[T]) = $this.zipWithIndex.minBy(_._1)._2 +} +``` + +### Translation of Trait Augmentations + +Now, assume a trait augmentation + + augment @ extends { } + +Let again `(, )` be the decomposition of ``. This augmentation is translated to + + implicit class ($this: ) extends { } + +As before, `` results from `` by replacing any occurrence of `this` with `$this`. However, all +parameters in now stay on the class definition, instead of being distributed to all members in ``. This is necessary in general, since `` might contain value definitions or other statements that cannot be +parameterized. + +For example, the trait augmentation + +```scala +class Test { + augment (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } +} +``` + +would be translated to + +```scala +class Test { + implicit class Test$$_augment_T_to_HasEql_1 [T] + ($this: T)(implicit $ev: Eql[T]) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql($this, that) + } +} +``` + +where the name `Test$$_augment_T_to_HasEql_1` is compiler generated and implementation dependent. \ No newline at end of file diff --git a/docs/docs/reference/dropped/implicit-value-classes.md b/docs/docs/reference/dropped/implicit-value-classes.md new file mode 100644 index 000000000000..857a83bd23b0 --- /dev/null +++ b/docs/docs/reference/dropped/implicit-value-classes.md @@ -0,0 +1,35 @@ +--- +layout: doc-page +title: Dropped: Implicit Classes and Value Classes +--- + +Scala uses implicit classes to define extension methods and conversions. E.g., from `Predef.scala`: + +```scala +implicit final class ArrowAssoc[A](private val self: A) extends AnyVal { + def -> [B](y: B): (A, B) = (self, y) +} +``` + +Implicit classes and value classes are still supported in Dotty, in order to support cross compilation with Scala 2. But they will be phased out in the future. The `ArrowAssoc` class can be written as an [augmentation](../augments/method-augments.html) as follows: + +```scala +augment (type A) { + def -> [B](that: B): (A, B) = (this, that) +} +``` + +Most other uses of value classes can be expressed by [opaque type aliases](../opaque.html). +There are only two aspects of value classes that are not covered: + + - A user-definable `toString` method. E.g. if `Meter` is a value class implemented in terms of `Double`, it is possible to define `toString` to that `new Meter(10).toString` prints `10m` instead of `10`. + + - Type tests and checked type casts for value classes. E.g. if `x: Any` once can have a pattern match like this: + + x match { case m: Meter => ... } + + The match succeeds if `x` is a `Meter` but not if it is `Double`. By contrast, an implementation in terms of + opaque type aliases would flag the match with an unchecked warning and succeed for both. This is a consequence of using the same runtime representation for `Meter` and `Double`. + +Given the quite high complexity of value classes, in terms of rules what is legal and what is not, and in terms of their boxing model, it is attractive to drop them. The combinaton of opaque types and augmentations is both simpler and generally more pleasant to use. + diff --git a/docs/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..b20c25d1c30a 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: Augmentations + subsection: + - title: Method Augmentations + url: docs/reference/augments/method-augments.html + - title: Trait Augmentations + url: docs/reference/augments/trait-augments.html + - title: Translation of Augmentations + url: docs/reference/augments/translation.html - title: Enums subsection: - title: Enumerations @@ -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/augment.scala b/tests/neg/augment.scala new file mode 100644 index 000000000000..aef5bed34b75 --- /dev/null +++ b/tests/neg/augment.scala @@ -0,0 +1,123 @@ +import Predef.{any2stringadd => _, _} +object augments { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + augment Circle { + def circumference = this.radius * math.Pi * 2 + private val p = math.Pi // error: `def` expected + } + + augment Circle { + def circumference = radius * math.Pi * 2 // error: not found + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + augment Circle extends HasArea { + def area = this.radius * this.radius * math.Pi + } + +// Generic trait implementations + + augment List[type T] { + type I = Int // error: `def` expected + def second = this.tail.head + } + +// Specific trait implementations + + augment List[Int] { self => // error: `def` expected + import java.lang._ // error: `def` expected + def maxx = (0 /: this)(_ `max` _) + } + + augment Array[Int] { + def maxx = (0 /: this)(_ `max` _) + } + +// Conditional extension methods + + case class Rectangle[T](x: T, y: T, width: T, height: T) + + trait Eql[T] { + def eql (x: T, y: T): Boolean + } + + augment Rectangle[type T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic augments + + augment (type T) { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic augments + + trait HasEql[T] { + def === (that: T): Boolean + } + + augment (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } + + augment List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } +} + +object augments2 { + import augments.Eql + // Nested generic arguments + + augment flatLists @ List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + augment samePairs @ (type T: Eql, T) { + def isSame: Boolean = this._1 === this._2 // error: === is not a member + } + +} + +import augments._ +import augments2._ +object Test extends App { + val c = Circle(0, 1, 2) + println(c.area) + + implicit object IntHasEql extends Eql[Int] { + def eql (x: Int, y: Int): Boolean = x == y + } + + println(1 ~ "a") + + val r1 = Rectangle(0, 0, 2, 2) + val r2 = Rectangle(0, 0, 2, 3) + println(r1.isSquare) + println(r2.isSquare) + println(r1 === r1) + println(r1 === r2) + println(List(1, 2, 3).second) + println(List(List(1), List(2, 3)).flattened) // error: type error + note that implicit conversions are ambiguous + println(Array(1, 2, 3).maxx) + println((2, 3).isSame) + println((3, 3).isSame) +} \ No newline at end of file diff --git a/tests/neg/gadt-eval.scala b/tests/neg/gadt-eval.scala index fad0fc04700e..091a1eab354b 100644 --- a/tests/neg/gadt-eval.scala +++ b/tests/neg/gadt-eval.scala @@ -20,7 +20,7 @@ object Test { def eval2[T](e: Exp[T]): T = e match { case e: Lit => e.value - case e: Pair[t1, t2] => + case e: Pair[type t1, type t2] => (eval(e.fst), eval(e.fst)) // error: //-- [E007] Type Mismatch Error: tests/neg/gadt-eval.scala:24:6 ------------------ //24 | (eval(e.fst), eval(e.fst)) diff --git a/tests/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/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..d7d5364b074d --- /dev/null +++ b/tests/pos/opaque-augment.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + object Logarithm { + + // These are the ways to lift to the logarithm type + def apply(d: Double): Logarithm = math.log(d) + + def safe(d: Double): Option[Logarithm] = + if (d > 0.0) Some(math.log(d)) else None + + // This is the first way to unlift the logarithm type + def exponent(l: Logarithm): Double = l + + augment Logarithm { + // This is the second way to unlift the logarithm type + def toDouble: Double = math.exp(this) + def +(that: Logarithm): Logarithm = Logarithm(math.exp(this) + math.exp(that)) + def *(that: Logarithm): Logarithm = Logarithm(this + that) + } + } +} +object usesites { + import opaquetypes._ + val l = Logarithm(1.0) + val l2 = Logarithm(2.0) + val l3 = l * l2 + val l4 = l + l2 // currently requires any2stringadd to be disabled because + // as a contextual implicit this takes precedence over the + // implicit scope implicit LogarithmOps. + // TODO: Remove any2stringadd + val d = l3.toDouble + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} 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/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/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/augment.scala b/tests/run/augment.scala new file mode 100644 index 000000000000..f1826ab772a1 --- /dev/null +++ b/tests/run/augment.scala @@ -0,0 +1,165 @@ +import Predef.{any2stringadd => _, _} +object augments { + +// Simple extension methods + + case class Circle(x: Double, y: Double, radius: Double) + + augment Circle { + def circumference = this.radius * math.Pi * 2 + } + +// Trait implementations + + trait HasArea { + def area: Double + } + + augment Circle extends HasArea { + def area = this.radius * this.radius * math.Pi + } + +// Generic augmentations + + augment List[type T] { + def second = this.tail.head + } + +// Specific augmentations + + augment List[Int] { + def maxx = (0 /: this)(_ `max` _) + } + + augment Array[Int] { + def maxx = (0 /: this)(_ `max` _) + } + +// Conditional extension methods + + case class Rectangle[T](x: T, y: T, width: T, height: T) + + trait Eql[T] { + def eql (x: T, y: T): Boolean + } + + augment Rectangle[type T: Eql] { + def isSquare: Boolean = implicitly[Eql[T]].eql(this.width, this.height) + } + + augment Rectangle[type T](implicit ev: Eql[T]) { + def isNotSquare: Boolean = !implicitly[Eql[T]].eql(this.width, this.height) + } + +// Simple generic augments + + augment (type T) { + def ~[U](that: U): (T, U) = (this, that) + } + +// Conditional generic augments + + trait HasEql[T] { + def === (that: T): Boolean + } + + augment eqlToHasEql @ (type T: Eql) extends HasEql[T] { + def === (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment (type T)(implicit ev: Eql[T]) { + def ==== (that: T): Boolean = implicitly[Eql[T]].eql(this, that) + } + + augment Rectangle[type T: Eql] extends HasEql[Rectangle[T]] { + def === (that: Rectangle[T]) = + this.x === that.x && + this.y === that.y && + this.width == that.width && + this.height == that.height + } +} + +object augments2 { + import augments.{Eql, eqlToHasEql} + // Nested generic arguments + + augment flatLists @ List[List[type U]] { + def flattened: List[U] = (this :\ (Nil: List[U]))(_ ++ _) + } + + augment samePairs @ (type T: Eql, T) { + def isSame = this._1 === this._2 + } + +} + +object docs { + augment Seq[String] { + def longestStrings: Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) + } + } + + augment List[List[type T]] { + def flattened: List[T] = this.foldLeft[List[T]](Nil)(_ ++ _) + } + + augment Seq[type T: math.Ordering] { + def indexOfLargest = this.zipWithIndex.maxBy(_._1)._2 + def indexOfSmallest = this.zipWithIndex.minBy(_._1)._2 + } + + object PostConditions { + opaque type EnsureResult[T] = T + + private object EnsureResult { + def wrap[T](x: T): EnsureResult[T] = x + def unwrap[T](x: EnsureResult[T]): T = x + } + + def result[T](implicit er: EnsureResult[T]): T = EnsureResult.unwrap(er) + + augment (type T) { + def ensuring[U](f: implicit EnsureResult[T] => Boolean): T = { + assert(f(EnsureResult.wrap(this))) + this + } + } + } + import PostConditions._ + + val s = List(1, 2, 3).sum.ensuring(result == 6) + +} + +import augments._ +import augments2.{flatLists, samePairs} +object Test extends App { + 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(samePairs((3, 3)).isSame) +} \ No newline at end of file