diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index b0c930cdbb51..781885faec65 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -3,7 +3,7 @@ package dotty.tools.backend.jvm import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.Trees import dotty.tools.dotc -import dotty.tools.dotc.core.Flags.FlagSet +import dotty.tools.dotc.core.Flags.{termFlagSet, termFlagConjunction} import dotty.tools.dotc.transform.{Erasure, GenericSignatures} import dotty.tools.dotc.transform.SymUtils._ import java.io.{File => _} @@ -801,7 +801,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def freshLocal(cunit: CompilationUnit, name: String, tpe: Type, pos: Position, flags: Flags): Symbol = { - ctx.newSymbol(sym, name.toTermName, FlagSet(flags), tpe, NoSymbol, pos) + ctx.newSymbol(sym, name.toTermName, termFlagSet(flags), tpe, NoSymbol, pos) } def getter(clz: Symbol): Symbol = decorateSymbol(sym).getter @@ -881,7 +881,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def =:=(other: Type): Boolean = tp =:= other def membersBasedOnFlags(excludedFlags: Flags, requiredFlags: Flags): List[Symbol] = - tp.membersBasedOnFlags(FlagSet(requiredFlags), FlagSet(excludedFlags)).map(_.symbol).toList + tp.membersBasedOnFlags(termFlagConjunction(requiredFlags), termFlagSet(excludedFlags)).map(_.symbol).toList def resultType: Type = tp.resultType diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 9db97de44590..55144d460145 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -4,7 +4,7 @@ package ast import util.Positions._ import core.Contexts.Context import core.Decorators._ -import core.Flags.JavaDefined +import core.Flags.{JavaDefined, Extension} import core.StdNames.nme /** A base class for things that have positions (currently: modifiers and trees) @@ -208,6 +208,22 @@ abstract class Positioned extends Product { // Leave out tparams, they are copied with wrong positions from parent class check(tree.mods) check(tree.vparamss) + case tree: DefDef if tree.mods.is(Extension) => + tree.vparamss match { + case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) => + check(vparams2) + check(tree.tparams) + check(vparams1) + check(rest) + case vparams1 :: rest => + check(vparams1) + check(tree.tparams) + check(rest) + case _ => + check(tree.tparams) + } + check(tree.tpt) + check(tree.rhs) case _ => val end = productArity var n = 0 diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 69d8b32bd36e..88b0dd515bd8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -345,9 +345,9 @@ object Trees { def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(untpd.Modifiers(flags)) - def setComment(comment: Option[Comment]): ThisTree[Untyped] = { + def setComment(comment: Option[Comment]): this.type = { comment.map(putAttachment(DocComment, _)) - asInstanceOf[ThisTree[Untyped]] + this } /** Destructively update modifiers. To be used with care. */ diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index 0c888b849857..048558a2211f 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 debug = noPrinter val dottydoc: Printer = noPrinter val exhaustivity: Printer = noPrinter val gadts: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index c4357138abad..15eb8c1e65a9 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -110,10 +110,10 @@ object Denotations { */ def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation - /** Keep only those denotations in this group whose flags do not intersect - * with `excluded`. + /** Keep only those denotations in this group that have all of the flags in `required`, + * but none of the flags in `excluded`. */ - def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation + def filterWithFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): PreDenotation private[this] var cachedPrefix: Type = _ private[this] var cachedAsSeenFrom: AsSeenFromResult = _ @@ -254,13 +254,12 @@ object Denotations { */ def accessibleFrom(pre: Type, superAccess: Boolean = false)(implicit ctx: Context): Denotation - /** Find member of this denotation with given name and - * produce a denotation that contains the type of the member - * as seen from given prefix `pre`. Exclude all members that have - * flags in `excluded` from consideration. + /** Find member of this denotation with given `name`, all `required` + * flags and no `excluded` flag, and produce a denotation that contains the type of the member + * as seen from given prefix `pre`. */ - def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = - info.findMember(name, pre, excluded) + def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = + info.findMember(name, pre, required, excluded) /** If this denotation is overloaded, filter with given predicate. * If result is still overloaded throw a TypeError. @@ -1076,16 +1075,17 @@ object Denotations { d >= Signature.ParamMatch && info.matches(other.info) } - final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = - if (p(this)) this else NoDenotation - final def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): SingleDenotation = - if (denots.exists && denots.matches(this)) NoDenotation else this def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(implicit ctx: Context): SingleDenotation = if (hasUniqueSym && prevDenots.containsSym(symbol)) NoDenotation else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) else asSeenFrom(pre).filterDisjoint(ownDenots) - final def filterExcluded(excluded: FlagSet)(implicit ctx: Context): SingleDenotation = - if (excluded.isEmpty || !(this overlaps excluded)) this else NoDenotation + + final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = + if (p(this)) this else NoDenotation + final def filterDisjoint(denots: PreDenotation)(implicit ctx: Context): SingleDenotation = + if (denots.exists && denots.matches(this)) NoDenotation else this + def filterWithFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): SingleDenotation = + if (required.isEmpty && excluded.isEmpty || compatibleWith(required, excluded)) this else NoDenotation type AsSeenFromResult = SingleDenotation protected def computeAsSeenFrom(pre: Type)(implicit ctx: Context): SingleDenotation = { @@ -1098,9 +1098,14 @@ object Denotations { else derivedSingleDenotation(symbol, symbol.info.asSeenFrom(pre, owner)) } - private def overlaps(fs: FlagSet)(implicit ctx: Context): Boolean = this match { - case sd: SymDenotation => sd is fs - case _ => symbol is fs + /** Does this denotation have all the `required` flags but none of the `excluded` flags? + */ + private def compatibleWith(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Boolean = { + val symd: SymDenotation = this match { + case symd: SymDenotation => symd + case _ => symbol.denot + } + symd.is(required) && !symd.is(excluded) } } @@ -1179,15 +1184,15 @@ object Denotations { def last: Denotation = denot2.last def matches(other: SingleDenotation)(implicit ctx: Context): Boolean = denot1.matches(other) || denot2.matches(other) + def mapInherited(owndenot: PreDenotation, prevdenot: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = + derivedUnion(denot1.mapInherited(owndenot, prevdenot, pre), denot2.mapInherited(owndenot, prevdenot, pre)) def filterWithPredicate(p: SingleDenotation => Boolean): PreDenotation = derivedUnion(denot1 filterWithPredicate p, denot2 filterWithPredicate p) def filterDisjoint(denot: PreDenotation)(implicit ctx: Context): PreDenotation = derivedUnion(denot1 filterDisjoint denot, denot2 filterDisjoint denot) - def mapInherited(owndenot: PreDenotation, prevdenot: PreDenotation, pre: Type)(implicit ctx: Context): PreDenotation = - derivedUnion(denot1.mapInherited(owndenot, prevdenot, pre), denot2.mapInherited(owndenot, prevdenot, pre)) - def filterExcluded(excluded: FlagSet)(implicit ctx: Context): PreDenotation = - derivedUnion(denot1.filterExcluded(excluded), denot2.filterExcluded(excluded)) - protected def derivedUnion(denot1: PreDenotation, denot2: PreDenotation): PreDenotation = + def filterWithFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): PreDenotation = + derivedUnion(denot1.filterWithFlags(required, excluded), denot2.filterWithFlags(required, excluded)) + protected def derivedUnion(denot1: PreDenotation, denot2: PreDenotation) = if ((denot1 eq this.denot1) && (denot2 eq this.denot2)) this else denot1 union denot2 } diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 86d8f1c83ddb..26a71b41747b 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -37,6 +37,9 @@ object Flags { else FlagSet(tbits | ((this.bits & ~that.bits) & ~KINDFLAGS)) } + def ^ (that: FlagSet) = + FlagSet((bits | that.bits) & KINDFLAGS | (bits ^ that.bits) & ~KINDFLAGS) + /** Does this flag set have a non-empty intersection with the given flag set? * This means that both the kind flags and the carrier bits have non-empty intersection. */ @@ -55,7 +58,7 @@ object Flags { */ def is(flags: FlagConjunction): Boolean = { val fs = bits & flags.bits - (fs & KINDFLAGS) != 0 && + ((fs & KINDFLAGS) != 0 || flags.bits == 0) && (fs >>> TYPESHIFT) == (flags.bits >>> TYPESHIFT) } @@ -119,6 +122,8 @@ object Flags { override def toString: String = flagStrings.mkString(" ") } + def termFlagSet(x: Long) = FlagSet(TERMS | x) + /** A class representing flag sets that should be tested * conjunctively. I.e. for a flag conjunction `fc`, * `x is fc` tests whether `x` contains all flags in `fc`. @@ -127,6 +132,8 @@ object Flags { override def toString: String = FlagSet(bits).toString } + def termFlagConjunction(x: Long) = FlagConjunction(TERMS | x) + private final val TYPESHIFT = 2 private final val TERMindex = 0 private final val TYPEindex = 1 @@ -179,6 +186,8 @@ object Flags { flag } + def allOf(flags: FlagSet) = FlagConjunction(flags.bits) + /** The conjunction of all flags in given flag set */ def allOf(flags1: FlagSet, flags2: FlagSet): FlagConjunction = { assert(flags1.numFlags == 1 && flags2.numFlags == 1, "Flags.allOf doesn't support flag " + (if (flags1.numFlags != 1) flags1 else flags2)) @@ -197,6 +206,8 @@ object Flags { /** The empty flag set */ final val EmptyFlags: FlagSet = FlagSet(0) + final val EmptyFlagConjunction = FlagConjunction(0) + /** The undefined flag set */ final val UndefinedFlags: FlagSet = FlagSet(~KINDFLAGS) @@ -334,6 +345,9 @@ object Flags { /** A method that has default params */ final val DefaultParameterized: FlagSet = termFlag(27, "") + /** An extension method */ + final val Extension = termFlag(28, "") + /** Symbol is defined by a Java class */ final val JavaDefined: FlagSet = commonFlag(30, "") @@ -466,7 +480,7 @@ object Flags { HigherKinded.toCommonFlags | Param | ParamAccessor.toCommonFlags | Scala2ExistentialCommon | MutableOrOpaque | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | - NonMember | ImplicitCommon | Permanent | Synthetic | + Extension.toCommonFlags | NonMember | ImplicitCommon | Permanent | Synthetic | SuperAccessorOrScala2x | Inline /** Flags that are not (re)set when completing the denotation, or, if symbol is @@ -588,6 +602,9 @@ object Flags { /** An inline parameter */ final val InlineParam: FlagConjunction = allOf(Inline, Param) + /** An extension method */ + final val ExtensionMethod = allOf(Method, Extension) + /** An enum case */ final val EnumCase: FlagConjunction = allOf(Enum, Case) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index fa41c4bc3573..311428e905b2 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -100,4 +100,7 @@ object Mode { /** Read comments from definitions when unpickling from TASTY */ val ReadComments: Mode = newMode(22, "ReadComments") + + /** Suppress insertion of apply or implicit conversion on qualifier */ + val FixedQualifier: Mode = newMode(23, "FixedQualifier") } diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 4f91adc45a54..5468cef6e01b 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -364,9 +364,9 @@ object Phases { assert(start <= Periods.MaxPossiblePhaseId, s"Too many phases, Period bits overflow") myBase = base myPeriod = Period(NoRunId, start, end) - myErasedTypes = prev.getClass == classOf[Erasure] || prev.erasedTypes - myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses - myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked + myErasedTypes = prev.getClass == classOf[Erasure] || prev.erasedTypes + myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses + myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked mySameMembersStartId = if (changesMembers) id else prev.sameMembersStartId mySameParentsStartId = if (changesParents) id else prev.sameParentsStartId mySameBaseTypesStartId = if (changesBaseTypes) id else prev.sameBaseTypesStartId diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 4b6261901d5f..c71965801142 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -177,6 +177,9 @@ object SymDenotations { if (myInfo.isInstanceOf[SymbolLoader]) FromStartFlags else AfterLoadFlags) + final def relevantFlagsFor(fs: FlagSet)(implicit ctx: Context) = + if (isCurrent(fs)) myFlags else flags + /** Has this denotation one of the flags in `fs` set? */ final def is(fs: FlagSet)(implicit ctx: Context): Boolean = (if (isCurrent(fs)) myFlags else flags) is fs @@ -860,7 +863,7 @@ object SymDenotations { /** The module implemented by this module class, NoSymbol if not applicable. */ final def sourceModule(implicit ctx: Context): Symbol = myInfo match { case ClassInfo(_, _, _, _, selfType) if this is ModuleClass => - def sourceOfSelf(tp: Any): Symbol = tp match { + def sourceOfSelf(tp: TypeOrSymbol): Symbol = tp match { case tp: TermRef => tp.symbol case tp: Symbol => sourceOfSelf(tp.info) case tp: RefinedType => sourceOfSelf(tp.parent) @@ -1668,9 +1671,9 @@ object SymDenotations { else collect(ownDenots, classParents) } - override final def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = { + override final def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = { val raw = if (excluded is Private) nonPrivateMembersNamed(name) else membersNamed(name) - raw.filterExcluded(excluded).asSeenFrom(pre).toDenot(pre) + raw.filterWithFlags(required, excluded).asSeenFrom(pre).toDenot(pre) } /** Compute tp.baseType(this) */ @@ -1917,7 +1920,7 @@ object SymDenotations { if (packageObjRunId != ctx.runId) { packageObjRunId = ctx.runId packageObjCache = NoDenotation // break cycle in case we are looking for package object itself - packageObjCache = findMember(nme.PACKAGE, thisType, EmptyFlags).asSymDenotation + packageObjCache = findMember(nme.PACKAGE, thisType, EmptyFlagConjunction, EmptyFlags).asSymDenotation } packageObjCache } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index b334ca2b1316..9318c9e063b2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -281,7 +281,7 @@ object TypeErasure { // From the spec, "Linearization also satisfies the property that a // linearization of a class always contains the linearization of its - // direct superclass as a suffix"; it's enought to consider every + // direct superclass as a suffix"; it's enough to consider every // candidate up to the first class. val candidates = takeUntil(tp2superclasses)(!_.is(Trait)) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d8d5d17182e5..3ac4820b1385 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -504,7 +504,7 @@ object Types { */ @tailrec final def findDecl(name: Name, excluded: FlagSet)(implicit ctx: Context): Denotation = this match { case tp: ClassInfo => - tp.decls.denotsNamed(name).filterExcluded(excluded).toDenot(NoPrefix) + tp.decls.denotsNamed(name).filterWithFlags(EmptyFlagConjunction, excluded).toDenot(NoPrefix) case tp: TypeProxy => tp.underlying.findDecl(name, excluded) case err: ErrorType => @@ -515,29 +515,29 @@ object Types { /** The member of this type with the given name */ final def member(name: Name)(implicit ctx: Context): Denotation = /*>|>*/ track("member") /*<|<*/ { - memberExcluding(name, EmptyFlags) + memberBasedOnFlags(name, required = EmptyFlagConjunction, excluded = EmptyFlags) } /** The non-private member of this type with the given name. */ final def nonPrivateMember(name: Name)(implicit ctx: Context): Denotation = track("nonPrivateMember") { - memberExcluding(name, Flags.Private) + memberBasedOnFlags(name, required = EmptyFlagConjunction, excluded = Flags.Private) } - final def memberExcluding(name: Name, excluding: FlagSet)(implicit ctx: Context): Denotation = { + /** The member with given `name` and required and/or excluded flags */ + final def memberBasedOnFlags(name: Name, required: FlagConjunction = EmptyFlagConjunction, excluded: FlagSet = EmptyFlags)(implicit ctx: Context): Denotation = { // We need a valid prefix for `asSeenFrom` val pre = this match { case tp: ClassInfo => tp.appliedRef case _ => widenIfUnstable } - findMember(name, pre, excluding) + findMember(name, pre, required, excluded) } - /** Find member of this type with given name and - * produce a denotation that contains the type of the member - * as seen from given prefix `pre`. Exclude all members that have - * flags in `excluded` from consideration. + /** Find member of this type with given `name`, all `required` + * flags and no `excluded` flag and produce a denotation that contains + * the type of the member as seen from given prefix `pre`. */ - final def findMember(name: Name, pre: Type, excluded: FlagSet)(implicit ctx: Context): Denotation = { + final def findMember(name: Name, pre: Type, required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Denotation = { @tailrec def go(tp: Type): Denotation = tp match { case tp: TermRef => go (tp.underlying match { @@ -547,7 +547,7 @@ object Types { }) case tp: TypeRef => tp.denot match { - case d: ClassDenotation => d.findMember(name, pre, excluded) + case d: ClassDenotation => d.findMember(name, pre, required, excluded) case d => go(d.info) } case tp: AppliedType => @@ -579,7 +579,7 @@ object Types { case tp: TypeProxy => go(tp.underlying) case tp: ClassInfo => - tp.cls.findMember(name, pre, excluded) + tp.cls.findMember(name, pre, required, excluded) case AndType(l, r) => goAnd(l, r) case tp: OrType => @@ -589,7 +589,7 @@ object Types { // supertype of `pre`. go(tp.join) case tp: JavaArrayType => - defn.ObjectType.findMember(name, pre, excluded) + defn.ObjectType.findMember(name, pre, required, excluded) case err: ErrorType => ctx.newErrorSymbol(pre.classSymbol orElse defn.RootClass, name, err.msg) case _ => @@ -796,10 +796,10 @@ object Types { (name, buf) => buf ++= member(name).altsWith(x => !x.is(Method))) } - /** The set of members of this type having at least one of `requiredFlags` but none of `excludedFlags` set */ - final def membersBasedOnFlags(requiredFlags: FlagSet, excludedFlags: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("membersBasedOnFlags") { + /** The set of members of this type that have all of `required` flags but none of `excluded` flags set. */ + final def membersBasedOnFlags(required: FlagConjunction, excluded: FlagSet)(implicit ctx: Context): Seq[SingleDenotation] = track("membersBasedOnFlags") { memberDenots(takeAllFilter, - (name, buf) => buf ++= memberExcluding(name, excludedFlags).altsWith(x => x.is(requiredFlags))) + (name, buf) => buf ++= memberBasedOnFlags(name, required, excluded).alternatives) } /** All members of this type. Warning: this can be expensive to compute! */ @@ -1379,6 +1379,9 @@ object Types { */ def deepenProto(implicit ctx: Context): Type = this + /** If this is an ignored proto type, its underlying type, otherwise the type itself */ + def revealIgnored: Type = this + // ----- Substitutions ----------------------------------------------------- /** Substitute all types that refer in their symbol attribute to diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 8db9e0a4d277..9172fc2049e9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -202,6 +202,7 @@ Standard-Section: "ASTs" TopLevelStat* SCALA2X // Imported from Scala2.x DEFAULTparameterized // Method with default parameters STABLE // Method that is assumed to be stable + EXTENSION // An extension method PARAMsetter // A setter without a body named `x_=` where `x` is pickled as a PARAM Annotation @@ -309,7 +310,8 @@ object TastyFormat { final val MACRO = 33 final val ERASED = 34 final val OPAQUE = 35 - final val PARAMsetter = 36 + final val EXTENSION = 36 + final val PARAMsetter = 37 // Cat. 2: tag Nat @@ -479,6 +481,7 @@ object TastyFormat { | SCALA2X | DEFAULTparameterized | STABLE + | EXTENSION | PARAMsetter | ANNOTATION | PRIVATEqualified @@ -538,6 +541,7 @@ object TastyFormat { case SCALA2X => "SCALA2X" case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" + case EXTENSION => "EXTENSION" case PARAMsetter => "PARAMsetter" case SHAREDterm => "SHAREDterm" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0d6b7a52915d..950863fa565f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -652,6 +652,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is CaseAccessor) writeByte(CASEaccessor) if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is Stable) writeByte(STABLE) + if (flags is Extension) writeByte(EXTENSION) if (flags is ParamAccessor) writeByte(PARAMsetter) assert(!(flags is Label)) } else { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index b97c48194813..bc8a8cc53595 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -629,6 +629,7 @@ class TreeUnpickler(reader: TastyReader, case SCALA2X => addFlag(Scala2x) case DEFAULTparameterized => addFlag(DefaultParameterized) case STABLE => addFlag(Stable) + case EXTENSION => addFlag(Extension) case PARAMsetter => addFlag(ParamAccessor) case PRIVATEqualified => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 70541b54857b..cc42e77d38d6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1022,7 +1022,7 @@ object Parsers { def typedOpt(): Tree = if (in.token == COLON) { in.nextToken(); toplevelTyp() } - else TypeTree() + else TypeTree().withPos(Position(in.lastOffset)) def typeDependingOn(location: Location.Value): Tree = if (location == Location.InParens) typ() @@ -1968,15 +1968,20 @@ object Parsers { * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param * Param ::= id `:' ParamType [`=' Expr] + * + * @return the list of parameter definitions */ - def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { - var imods: Modifiers = EmptyModifiers + def paramClause(ofClass: Boolean = false, // owner is a class + ofCaseClass: Boolean = false, // owner is a case class + prefix: Boolean = false, // clause precedes name of an extension method + firstClause: Boolean = false) // clause is the first in regular list of clauses + : List[ValDef] = { var implicitOffset = -1 // use once - var firstClauseOfCaseClass = ofCaseClass - def param(): ValDef = { + + def param(impliedMods: Modifiers): ValDef = { val start = in.offset - var mods = annotsAsMods() - if (owner.isTypeName) { + var mods = impliedMods.withAnnotations(annotations()) + if (ofClass) { mods = addFlag(modifiers(start = mods), ParamAccessor) mods = atPos(start, in.offset) { @@ -1989,9 +1994,9 @@ object Parsers { addMod(mods, mod) } else { - if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) + if (!(mods.flags &~ (ParamAccessor | Inline | impliedMods.flags)).isEmpty) syntaxError("`val' or `var' expected") - if (firstClauseOfCaseClass) mods + if (firstClause && ofCaseClass) mods else mods | PrivateLocal } } @@ -2004,7 +2009,7 @@ object Parsers { atPos(start, nameStart) { val name = ident() accept(COLON) - if (in.token == ARROW && owner.isTypeName && !(mods is Local)) + if (in.token == ARROW && ofClass && !(mods is Local)) syntaxError(VarValParametersMayNotBeCallByName(name, mods is Mutable)) val tpt = paramType() val default = @@ -2014,56 +2019,73 @@ object Parsers { mods = mods.withPos(mods.pos.union(Position(implicitOffset, implicitOffset))) implicitOffset = -1 } - for (imod <- imods.mods) mods = addMod(mods, imod) ValDef(name, tpt, default).withMods(mods) } } - def paramClause(): List[ValDef] = inParens { - if (in.token == RPAREN) Nil + + def checkVarArgsRules(vparams: List[ValDef]): Unit = vparams match { + case Nil => + case _ :: Nil if !prefix => + case vparam :: rest => + vparam.tpt match { + case PostfixOp(_, op) if op.name == tpnme.raw.STAR => + syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) + case _ => + } + checkVarArgsRules(rest) + } + + // begin paramClause + inParens { + if (in.token == RPAREN && !prefix) Nil else { - def funArgMods(): Unit = { + def funArgMods(mods: Modifiers): Modifiers = if (in.token == IMPLICIT) { implicitOffset = in.offset - imods = addMod(imods, atPos(accept(IMPLICIT)) { Mod.Implicit() }) - funArgMods() - } else if (in.token == ERASED) { - imods = addMod(imods, atPos(accept(ERASED)) { Mod.Erased() }) - funArgMods() + funArgMods(addMod(mods, atPos(accept(IMPLICIT)) { Mod.Implicit() })) } - } - funArgMods() - - commaSeparated(() => param()) + else if (in.token == ERASED) + funArgMods(addMod(mods, atPos(accept(ERASED)) { Mod.Erased() })) + else mods + + val paramMods = funArgMods(EmptyModifiers) + val clause = + if (prefix) param(paramMods) :: Nil + else commaSeparated(() => param(paramMods)) + checkVarArgsRules(clause) + clause } } - def clauses(): List[List[ValDef]] = { + } + + /** ClsParamClauses ::= {ClsParamClause} + * DefParamClauses ::= {DefParamClause} + * + * @return The parameter definitions + */ + def paramClauses(ofClass: Boolean = false, + ofCaseClass: Boolean = false): (List[List[ValDef]]) = { + def recur(firstClause: Boolean): List[List[ValDef]] = { newLineOptWhenFollowedBy(LPAREN) if (in.token == LPAREN) { - imods = EmptyModifiers - paramClause() :: { - firstClauseOfCaseClass = false - if (imods is Implicit) Nil else clauses() - } - } else Nil - } - val start = in.offset - val result = clauses() - if (owner == nme.CONSTRUCTOR && (result.isEmpty || (result.head take 1 exists (_.mods is Implicit)))) { - in.token match { - case LBRACKET => syntaxError("no type parameters allowed here") - case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) - case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), start) + val params = paramClause( + ofClass = ofClass, + ofCaseClass = ofCaseClass, + firstClause = firstClause) + val lastClause = + params.nonEmpty && params.head.mods.flags.is(Implicit) + params :: (if (lastClause) Nil else recur(firstClause = false)) } + else Nil } - val listOfErrors = checkVarArgsRules(result) - listOfErrors.foreach { vparam => - syntaxError(VarArgsParamMustComeLast(), vparam.tpt.pos) - } - result + recur(firstClause = true) } /* -------- DEFS ------------------------------------------- */ + def finalizeDef(md: MemberDef, mods: Modifiers, start: Int): md.ThisTree[Untyped] = + md.withMods(mods).setComment(in.getDocComment(start)) + /** Import ::= import ImportExpr {`,' ImportExpr} */ def importClause(): List[Tree] = { @@ -2189,29 +2211,16 @@ object Parsers { } else EmptyTree lhs match { case (id @ Ident(name: TermName)) :: Nil => { - ValDef(name, tpt, rhs).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(ValDef(name, tpt, rhs), mods, start) } case _ => PatDef(mods, lhs, tpt, rhs) } } - private def checkVarArgsRules(vparamss: List[List[ValDef]]): List[ValDef] = { - def isVarArgs(tpt: Trees.Tree[Untyped]): Boolean = tpt match { - case PostfixOp(_, op) if op.name == tpnme.raw.STAR => true - case _ => false - } - - vparamss.flatMap { params => - if (params.nonEmpty) { - params.init.filter(valDef => isVarArgs(valDef.tpt)) - } else List() - } - } - /** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr * | this ParamClause ParamClauses `=' ConstrExpr * DefDcl ::= DefSig `:' Type - * DefSig ::= id [DefTypeParamClause] ParamClauses + * DefSig ::= ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] ParamClauses */ def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) { def scala2ProcedureSyntax(resultTypeStr: String) = { @@ -2225,7 +2234,13 @@ object Parsers { } if (in.token == THIS) { in.nextToken() - val vparamss = paramClauses(nme.CONSTRUCTOR) + val vparamss = paramClauses() + if (vparamss.isEmpty || vparamss.head.take(1).exists(_.mods.is(Implicit))) + in.token match { + case LBRACKET => syntaxError("no type parameters allowed here") + case EOF => incompleteInputError(AuxConstructorNeedsNonImplicitParameter()) + case _ => syntaxError(AuxConstructorNeedsNonImplicitParameter(), nameStart) + } if (in.isScala2Mode) newLineOptWhenFollowedBy(LBRACE) val rhs = { if (!(in.token == LBRACE && scala2ProcedureSyntax(""))) accept(EQUALS) @@ -2233,10 +2248,20 @@ object Parsers { } makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start)) } else { - val mods1 = addFlag(mods, Method) + val (leadingParamss: List[List[ValDef]], flags: FlagSet) = + if (in.token == LPAREN) + (paramClause(prefix = true) :: Nil, Method | Extension) + else + (Nil, Method) + val mods1 = addFlag(mods, flags) val name = ident() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses(name) + val vparamss = paramClauses() match { + case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(name) => + rparams :: leadingParamss ::: rparamss + case rparamss => + leadingParamss ::: rparamss + } var tpt = fromWithinReturnType { if (in.token == SUBTYPE && mods.is(Inline)) { in.nextToken() @@ -2262,7 +2287,7 @@ object Parsers { accept(EQUALS) expr() } - DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1).setComment(in.getDocComment(start)) + finalizeDef(DefDef(name, tparams, vparamss, tpt, rhs), mods1, start) } } @@ -2302,7 +2327,7 @@ object Parsers { val name = ident().toTypeName val tparams = typeParamClauseOpt(ParamOwner.Type) def makeTypeDef(rhs: Tree): Tree = - TypeDef(name, lambdaAbstract(tparams, rhs)).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(TypeDef(name, lambdaAbstract(tparams, rhs)), mods, start) in.token match { case EQUALS => in.nextToken() @@ -2355,23 +2380,23 @@ object Parsers { } def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = { - val constr = classConstr(name, isCaseClass = mods is Case) + val constr = classConstr(isCaseClass = mods.is(Case)) val templ = templateOpt(constr) - TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(TypeDef(name, templ), mods, start) } /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses */ - def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { + def classConstr(isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) - val cmods = fromWithinClassConstr(constrModsOpt(owner)) - val vparamss = paramClauses(owner, isCaseClass) + val cmods = fromWithinClassConstr(constrModsOpt()) + val vparamss = paramClauses(ofClass = true, ofCaseClass = isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } /** ConstrMods ::= {Annotation} [AccessModifier] */ - def constrModsOpt(owner: Name): Modifiers = + def constrModsOpt(): Modifiers = modifiers(accessModifierTokens, annotsAsMods()) /** ObjectDef ::= id TemplateOpt @@ -2382,7 +2407,7 @@ object Parsers { def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = { val template = templateOpt(emptyConstructor) - ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) + finalizeDef(ModuleDef(name, template), mods, start) } /** EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody @@ -2390,9 +2415,9 @@ object Parsers { def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): TypeDef = atPos(start, nameStart) { val modName = ident() val clsName = modName.toTypeName - val constr = classConstr(clsName) + val constr = classConstr() val impl = templateOpt(constr, isEnum = true) - TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) + finalizeDef(TypeDef(clsName, impl), addMod(mods, enumMod), start) } /** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids) @@ -2416,12 +2441,12 @@ object Parsers { val caseDef = if (in.token == LBRACKET || in.token == LPAREN || in.token == AT || isModifier) { val clsName = id.name.toTypeName - val constr = classConstr(clsName, isCaseClass = true) + val constr = classConstr(isCaseClass = true) TypeDef(clsName, caseTemplate(constr)) } else ModuleDef(id.name.toTermName, caseTemplate(emptyConstructor)) - caseDef.withMods(mods1).setComment(in.getDocComment(start)) + finalizeDef(caseDef, mods1, start) } } } @@ -2564,7 +2589,7 @@ object Parsers { case Typed(tree @ This(EmptyTypeIdent), tpt) => self = makeSelfDef(nme.WILDCARD, tpt).withPos(first.pos) case _ => - val ValDef(name, tpt, _) = convertToParam(first, expected = "self type clause") + val ValDef(name, tpt, _) = convertToParam(first, EmptyModifiers, "self type clause") if (name != nme.ERROR) self = makeSelfDef(name, tpt).withPos(first.pos) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 038d65920e32..46a9748bfd52 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -231,8 +231,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case _ => toTextGlobal(args, ", ") } return "FunProto(" ~ argsText ~ "):" ~ toText(resultType) - case tp: IgnoredProto => - return "?" + case IgnoredProto(ignored) => + return "?" ~ (("(ignored: " ~ toText(ignored) ~ ")") provided ctx.settings.verbose.value) case tp @ PolyProto(targs, resType) => return "PolyProto(" ~ toTextGlobal(targs, ", ") ~ "): " ~ toText(resType) case _ => @@ -662,9 +662,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def tparamsText[T >: Untyped](params: List[Tree[T]]): Text = "[" ~ toText(params, ", ") ~ "]" provided params.nonEmpty - def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]]): Text = - (txt /: vparamss)((txt, vparams) => txt ~ "(" ~ toText(vparams, ", ") ~ ")") - + def addVparamssText[T >: Untyped](txt: Text, vparamss: List[List[ValDef[T]]], isExtension: Boolean = false): Text = { + def paramsText(params: List[ValDef[T]]) = "(" ~ toText(params, ", ") ~ ")" + val (leading, paramss) = + if (isExtension && vparamss.nonEmpty) (paramsText(vparamss.head) ~ " " ~ txt, vparamss.tail) + else (txt, vparamss) + (txt /: paramss)((txt, params) => txt ~ paramsText(params)) + } protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { import untpd.{modsDeco => _} dclTextOr(tree) { @@ -678,9 +682,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { import untpd.{modsDeco => _} dclTextOr(tree) { val prefix = modText(tree.mods, tree.symbol, keywordStr("def"), isType = false) ~~ valDefText(nameIdText(tree)) + val isExtension = tree.hasType && tree.symbol.is(Extension) withEnclosingDef(tree) { - addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss) ~ optAscription(tree.tpt) ~ - optText(tree.rhs)(" = " ~ _) + addVparamssText(prefix ~ tparamsText(tree.tparams), tree.vparamss, isExtension) ~ + optAscription(tree.tpt) ~ + optText(tree.rhs)(" = " ~ _) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 6b1b7079b493..f4fab05d9f37 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -459,8 +459,7 @@ class TreeChecker extends Phase with SymTransformer { if (ctx.mode.isExpr && !tree.isEmpty && !isPrimaryConstructorReturn && - !pt.isInstanceOf[FunProto] && - !pt.isInstanceOf[PolyProto]) + !pt.isInstanceOf[FunOrPolyProto]) assert(tree.tpe <:< pt, { val mismatch = err.typeMismatchMsg(tree.tpe, pt) i"""|${mismatch.msg} diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index bf58b204d575..42c2580f78c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -833,8 +833,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { /** does the companion object of the given symbol have custom unapply */ def hasCustomUnapply(sym: Symbol): Boolean = { val companion = sym.companionModule - companion.findMember(nme.unapply, NoPrefix, excluded = Synthetic).exists || - companion.findMember(nme.unapplySeq, NoPrefix, excluded = Synthetic).exists + companion.findMember(nme.unapply, NoPrefix, required = EmptyFlagConjunction, excluded = Synthetic).exists || + companion.findMember(nme.unapplySeq, NoPrefix, required = EmptyFlagConjunction, excluded = Synthetic).exists } def doShow(s: Space, mergeList: Boolean = false): String = s match { diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index eea06175ecb0..9c7d5c36942d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -164,6 +164,12 @@ object Applications { def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context): Tree = if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree + + /** A wrapper indicating that its argument is an application of an extension method. + */ + case class ExtMethodApply(app: Tree) extends tpd.Tree { + override def pos = app.pos + } } @@ -778,13 +784,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * part. Return an optional value to indicate success. */ def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = - tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 => - tryEither { - implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] - } { - (_, _) => None + if (ctx.mode.is(Mode.FixedQualifier)) None + else + tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 => + tryEither { + implicit ctx => Some(simpleApply(fun2, proto)): Option[Tree] + } { + (_, _) => None + } } - } fun1.tpe match { case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs).withType(err) @@ -870,22 +878,26 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = track("typedTypeApply") { val isNamed = hasNamedArg(tree.args) val typedArgs = if (isNamed) typedNamedArgs(tree.args) else tree.args.mapconserve(typedType(_)) - val typedFn = typedExpr(tree.fun, PolyProto(typedArgs.tpes, pt)) - typedFn.tpe.widen match { - case pt: PolyType => - if (typedArgs.length <= pt.paramInfos.length && !isNamed) - if (typedFn.symbol == defn.Predef_classOf && typedArgs.nonEmpty) { - val arg = typedArgs.head - checkClassType(arg.tpe, arg.pos, traitReq = false, stablePrefixReq = false) - } - case _ => - } - def tryDynamicTypeApply(): Tree = typedFn match { - case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt) - case _ => tree.withType(TryDynamicCallType) + typedExpr(tree.fun, PolyProto(typedArgs, pt)) match { + case ExtMethodApply(app) => + app + case typedFn => + typedFn.tpe.widen match { + case pt: PolyType => + if (typedArgs.length <= pt.paramInfos.length && !isNamed) + if (typedFn.symbol == defn.Predef_classOf && typedArgs.nonEmpty) { + val arg = typedArgs.head + checkClassType(arg.tpe, arg.pos, traitReq = false, stablePrefixReq = false) + } + case _ => + } + def tryDynamicTypeApply(): Tree = typedFn match { + case typedFn: Select if !pt.isInstanceOf[FunProto] => typedDynamicSelect(typedFn, typedArgs, pt) + case _ => tree.withType(TryDynamicCallType) + } + if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() + else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) } - if (typedFn.tpe eq TryDynamicCallType) tryDynamicTypeApply() - else assignType(cpy.TypeApply(tree)(typedFn, typedArgs), typedFn, typedArgs) } /** Rewrite `new Array[T](....)` if T is an unbounded generic to calls to newGenericArray. @@ -1123,6 +1135,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic => tp.member(nme.apply).hasAltWith(d => p(TermRef(tp, nme.apply, d))) } + /** Does `tp` have an extension method named `name` with this-argument `argType` and + * result matching `resultType`? + */ + def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { + val mbr = tp.memberBasedOnFlags(name, required = allOf(ExtensionMethod)) + mbr.exists && isApplicable(tp.select(name, mbr), argType :: Nil, resultType) + } + /** Compare owner inheritance level. * @param sym1 The first owner * @param sym2 The second owner @@ -1331,16 +1351,16 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. */ - def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { - case IgnoredProto(ignored) => resultConforms(altSym, altType, ignored) - case _: ValueType => - altType.widen match { - case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) - case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) - case _ => true - } - case _ => true - } + def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = + resultType.revealIgnored match { + case resultType: ValueType => + altType.widen match { + case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) + case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) + case _ => true + } + case _ => true + } /** If the `chosen` alternative has a result type incompatible with the expected result * type `pt`, run overloading resolution again on all alternatives that do match `pt`. @@ -1472,7 +1492,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case pt @ PolyProto(targs1, pt1) if targs.isEmpty => val alts1 = alts filter pt.isMatchedBy - resolveOverloaded(alts1, pt1, targs1) + resolveOverloaded(alts1, pt1, targs1.tpes) case defn.FunctionOf(args, resultType, _, _) => narrowByTypes(alts, args, resultType) @@ -1622,5 +1642,25 @@ trait Applications extends Compatibility { self: Typer with Dynamic => */ private def harmonizeTypes(tpes: List[Type])(implicit ctx: Context): List[Type] = harmonizeWith(tpes)(identity, (tp, pt) => pt) + + /** The typed application + * + * () or + * []() + * + * where comes from `pt` if it is a PolyProto. + */ + def extMethodApply(methodRef: untpd.Tree, receiver: Tree, pt: Type)(implicit ctx: Context) = { + val (core, pt1) = pt.revealIgnored match { + case PolyProto(targs, restpe) => (untpd.TypeApply(methodRef, targs.map(untpd.TypedSplice(_))), restpe) + case _ => (methodRef, pt) + } + val app = + typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)( + ctx.addMode(Mode.FixedQualifier)) + if (!app.symbol.is(Extension)) + ctx.error(em"not an extension method: $methodRef", receiver.pos) + app + } } diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index f2c42f05262e..e9f9d6d5ea50 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -40,7 +40,7 @@ object ErrorReporting { def expectedTypeStr(tp: Type): String = tp match { case tp: PolyProto => - em"type arguments [${tp.targs}%, %] and ${expectedTypeStr(tp.resultType)}" + em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(tp.resultType)}" case tp: FunProto => val result = tp.resultType match { case _: WildcardType | _: IgnoredProto => "" diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 118aba4ea0e6..a2ae313476f9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -41,6 +41,13 @@ import scala.annotation.internal.sharable object Implicits { import tpd._ + /** A flag indicating that this an application of an extension method + * with the given name + */ + case class ExtMethodResult(app: Tree) extends tpd.Tree { + override def pos = app.pos + } + /** An implicit definition `implicitRef` that is visible under a different name, `alias`. * Gets generated if an implicit ref is imported via a renaming import. */ @@ -49,8 +56,18 @@ object Implicits { } /** An eligible implicit candidate, consisting of an implicit reference and a nesting level */ - case class Candidate(implicitRef: ImplicitRef, level: Int) { + case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) { def ref: TermRef = implicitRef.underlyingRef + + def isExtension = (kind & Candidate.Extension) != 0 + def isConversion = (kind & Candidate.Conversion) != 0 + } + object Candidate { + type Kind = Int + final val None = 0 + final val Value = 1 + final val Conversion = 2 + final val Extension = 4 } /** A common base class of contextual implicits and of-type implicits which @@ -78,64 +95,76 @@ object Implicits { /** Return those references in `refs` that are compatible with type `pt`. */ protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") { - def refMatches(ref: TermRef)(implicit ctx: Context) = /*trace(i"refMatches $ref $pt")*/ { - - def discardForView(tpw: Type, argType: Type): Boolean = tpw match { - case mt: MethodType => - mt.isImplicitMethod || - mt.paramInfos.lengthCompare(1) != 0 || - !ctx.test(implicit ctx => - argType relaxed_<:< widenSingleton(mt.paramInfos.head)) - case poly: PolyType => - // We do not need to call ProtoTypes#constrained on `poly` because - // `refMatches` is always called with mode TypevarsMissContext enabled. - poly.resultType match { - case mt: MethodType => - mt.isImplicitMethod || - mt.paramInfos.length != 1 || - !ctx.test(implicit ctx => - argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head))) - case rtp => - discardForView(wildApprox(rtp), argType) - } - case tpw: TermRef => - false // can't discard overloaded refs - case tpw => - // Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views. - // However, Predef.$conforms is not eligible, because it is a no-op. - // - // In principle, it would be cleanest if only implicit methods qualified - // as implicit conversions. We could achieve that by having standard conversions like - // this in Predef: - // - // implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a) - // implicit def convertIfConverter[A, B](x: A)(implicit ev: ImplicitConverter[A, B]): B = ev(a) - // - // (Once `<:<` inherits from `ImplicitConverter` we only need the 2nd one.) - // But clauses like this currently slow down implicit search a lot, because - // they are eligible for all pairs of types, and therefore are tried too often. - // We emulate instead these conversions directly in the search. - // The reason for leaving out `Predef_conforms` is that we know it adds - // nothing since it only relates subtype with supertype. - // - // We keep the old behavior under -language:Scala2. - val isFunctionInS2 = - ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms - val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter) - val isConforms = // An implementation of <:< counts as a view, except that $conforms is always omitted - tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms - !(isFunctionInS2 || isImplicitConverter || isConforms) - } + def candidateKind(ref: TermRef)(implicit ctx: Context): Candidate.Kind = /*trace(i"candidateKind $ref $pt")*/ { - def discardForValueType(tpw: Type): Boolean = tpw.stripPoly match { - case tpw: MethodType => !tpw.isImplicitMethod - case _ => false + def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = { + + def methodCandidateKind(mt: MethodType, formal: => Type) = + if (!mt.isImplicitMethod && + mt.paramInfos.lengthCompare(1) == 0 && + ctx.test(implicit ctx => argType relaxed_<:< formal)) + Candidate.Conversion + else + Candidate.None + + tpw match { + case mt: MethodType => + methodCandidateKind(mt, widenSingleton(mt.paramInfos.head)) + case poly: PolyType => + // We do not need to call ProtoTypes#constrained on `poly` because + // `candidateKind` is always called with mode TypevarsMissContext enabled. + poly.resultType match { + case mt: MethodType => + methodCandidateKind(mt, wildApprox(widenSingleton(mt.paramInfos.head))) + case rtp => + viewCandidateKind(wildApprox(rtp), argType, resType) + } + case tpw: TermRef => + Candidate.Conversion | Candidate.Extension // can't discard overloaded refs + case tpw => + // Only direct instances of Function1 and direct or indirect instances of <:< are eligible as views. + // However, Predef.$conforms is not eligible, because it is a no-op. + // + // In principle, it would be cleanest if only implicit methods qualified + // as implicit conversions. We could achieve that by having standard conversions like + // this in Predef: + // + // implicit def convertIfConforms[A, B](x: A)(implicit ev: A <:< B): B = ev(a) + // implicit def convertIfConverter[A, B](x: A)(implicit ev: ImplicitConverter[A, B]): B = ev(a) + // + // (Once `<:<` inherits from `ImplicitConverter` we only need the 2nd one.) + // But clauses like this currently slow down implicit search a lot, because + // they are eligible for all pairs of types, and therefore are tried too often. + // We emulate instead these conversions directly in the search. + // The reason for leaving out `Predef_conforms` is that we know it adds + // nothing since it only relates subtype with supertype. + // + // We keep the old behavior under -language:Scala2. + val isFunctionInS2 = + ctx.scala2Mode && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms + val isImplicitConverter = tpw.derivesFrom(defn.Predef_ImplicitConverter) + val isConforms = // An implementation of <:< counts as a view, except that $conforms is always omitted + tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms + val hasExtensions = resType match { + case SelectionProto(name, _, _, _) => + tpw.memberBasedOnFlags(name, required = allOf(ExtensionMethod)).exists + case _ => false + } + val conversionKind = + if (isFunctionInS2 || isImplicitConverter || isConforms) Candidate.Conversion + else Candidate.None + val extensionKind = + if (hasExtensions) Candidate.Extension + else Candidate.None + conversionKind | extensionKind + } } - def discard = pt match { - case pt: ViewProto => discardForView(ref.widen, pt.argType) - case _: ValueTypeOrProto => !defn.isFunctionType(pt) && discardForValueType(ref.widen) - case _ => false + def valueTypeCandidateKind(tpw: Type): Candidate.Kind = tpw.stripPoly match { + case tpw: MethodType => + if (tpw.isImplicitMethod) Candidate.Value else Candidate.None + case _ => + Candidate.Value } /** Widen singleton arguments of implicit conversions to their underlying type. @@ -143,7 +172,7 @@ object Implicits { * Note that we always take the underlying type of a singleton type as the argument * type, so that we get a reasonable implicit cache hit ratio. */ - def adjustSingletonArg(tp: Type): Type = tp match { + def adjustSingletonArg(tp: Type): Type = tp.widenSingleton match { case tp: PolyType => val res = adjustSingletonArg(tp.resType) if (res `eq` tp.resType) tp else tp.derivedLambdaType(resType = res) @@ -152,28 +181,44 @@ object Implicits { case _ => tp } - (ref.symbol isAccessibleFrom ref.prefix) && { - if (discard) { - record("discarded eligible") - false - } - else { - val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749 - val refAdjusted = - if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref.widenSingleton) - else ref - val refNorm = normalize(refAdjusted, pt) - NoViewsAllowed.isCompatible(refNorm, ptNorm) + var ckind = + if (!ref.symbol.isAccessibleFrom(ref.prefix)) Candidate.None + else pt match { + case pt: ViewProto => + viewCandidateKind(ref.widen, pt.argType, pt.resType) + case _: ValueTypeOrProto => + if (defn.isFunctionType(pt)) Candidate.Value + else valueTypeCandidateKind(ref.widen) + case _ => + Candidate.Value } + + if (ckind == Candidate.None) + record("discarded eligible") + else { + val ptNorm = normalize(pt, pt) // `pt` could be implicit function types, check i2749 + val refAdjusted = + if (pt.isInstanceOf[ViewProto]) adjustSingletonArg(ref) + else ref + val refNorm = normalize(refAdjusted, pt) + if (!NoViewsAllowed.isCompatible(refNorm, ptNorm)) + ckind = Candidate.None } + ckind } + if (refs.isEmpty) Nil else { val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) - refs - .filter(ref => nestedCtx.test(implicit ctx => refMatches(ref.underlyingRef))) - .map(Candidate(_, level)) + + def matchingCandidate(ref: ImplicitRef): Option[Candidate] = + nestedCtx.test(implicit ctx => candidateKind(ref.underlyingRef)) match { + case Candidate.None => None + case ckind => Some(new Candidate(ref, ckind, level)) + } + + refs.flatMap(matchingCandidate) } } } @@ -480,7 +525,8 @@ trait ImplicitRunInfo { self: Run => for (parent <- cls.classParents; ref <- iscopeRefs(tp.baseType(parent.classSymbol))) addRef(ref) } - if (tp.widen.typeSymbol.isOpaqueAlias) addCompanionOf(tp.widen.typeSymbol) + val underlyingTypeSym = tp.widen.typeSymbol + if (underlyingTypeSym.isOpaqueAlias) addCompanionOf(underlyingTypeSym) else tp.classSymbols(liftingCtx).foreach(addClassScope) case _ => for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) @@ -888,12 +934,28 @@ trait Implicits { self: Typer => val ref = cand.ref var generated: Tree = tpd.ref(ref).withPos(pos.startPos) val locked = ctx.typerState.ownedVars - if (!argument.isEmpty) - generated = typedUnadapted( - untpd.Apply(untpd.TypedSplice(generated), untpd.TypedSplice(argument) :: Nil), - pt, locked) - val generated1 = adapt(generated, pt, locked) - + val generated1 = + if (argument.isEmpty) + adapt(generated, pt, locked) + else { + val untpdGenerated = untpd.TypedSplice(generated) + def tryConversion(implicit ctx: Context) = + typed( + untpd.Apply(untpdGenerated, untpd.TypedSplice(argument) :: Nil), + pt, locked) + if (cand.isExtension) { + val SelectionProto(name: TermName, mbrType, _, _) = pt + val result = extMethodApply(untpd.Select(untpdGenerated, name), argument, mbrType) + if (!ctx.reporter.hasErrors && cand.isConversion) { + val testCtx = ctx.fresh.setExploreTyperState() + tryConversion(testCtx) + if (testCtx.reporter.hasErrors) + ctx.error(em"ambiguous implicit: $generated is eligible both as an implicit conversion and as an extension method container") + } + result + } + else tryConversion + } lazy val shadowing = typedUnadapted(untpd.Ident(cand.implicitRef.implicitName) withPos pos.toSynthetic)( nestedContext().addMode(Mode.ImplicitShadowing).setExploreTyperState()) @@ -934,8 +996,12 @@ trait Implicits { self: Typer => SearchFailure(generated1.withTypeUnchecked( new ShadowedImplicit(ref, methPart(shadowing).tpe, pt, argument))) } - else - SearchSuccess(generated1, ref, cand.level)(ctx.typerState) + else { + val generated2 = + if (cand.isExtension) Applications.ExtMethodApply(generated1).withType(generated1.tpe) + else generated1 + SearchSuccess(generated2, ref, cand.level)(ctx.typerState) + } }} /** Try to type-check implicit reference, after checking that this is not diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 1aef548cd2ae..78de0d2fc60b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -47,7 +47,7 @@ object Inliner { /** Should call be inlined in this context? */ def isInlineable(tree: Tree)(implicit ctx: Context): Boolean = tree match { case Block(_, expr) => isInlineable(expr) - case _ => isInlineable(tree.symbol) + case _ => isInlineable(tree.symbol) && !tree.tpe.isInstanceOf[MethodOrPoly] } /** Try to inline a call to an inline method. Fail with error if the maximal @@ -240,6 +240,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } computeParamBindings(tp.resultType, Nil, argss) case tp: MethodType => + assert(argss.nonEmpty, i"missing bindings: $tp in $call") (tp.paramNames, tp.paramInfos, argss.head).zipped.foreach { (name, paramtp, arg) => paramBinding(name) = arg.tpe.dealias match { case _: SingletonType if isIdempotentExpr(arg) => arg.tpe diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e1f5e59e3259..969ae304c052 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -41,20 +41,22 @@ trait NamerContextOps { this: Context => sym } - /** The denotation with the given name in current context */ - def denotNamed(name: Name): Denotation = + /** The denotation with the given `name` and all `required` flags in current context + */ + def denotNamed(name: Name, required: FlagConjunction = EmptyFlagConjunction): Denotation = if (owner.isClass) if (outer.owner == owner) { // inner class scope; check whether we are referring to self if (scope.size == 1) { val elem = scope.lastEntry if (elem.name == name) return elem.sym.denot // return self } - owner.thisType.member(name) + val pre = owner.thisType + pre.findMember(name, pre, required, EmptyFlags) } else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext. - owner.findMember(name, owner.thisType, EmptyFlags) + owner.findMember(name, owner.thisType, required, EmptyFlags) else - scope.denotsNamed(name).toDenot(NoPrefix) + scope.denotsNamed(name).filterWithFlags(required, EmptyFlags).toDenot(NoPrefix) /** Either the current scope, or, if the current context owner is a class, * the declarations of the current class. diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 46b2fb96025c..f71d6351a1e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -96,6 +96,7 @@ object ProtoTypes { /** A class marking ignored prototypes that can be revealed by `deepenProto` */ case class IgnoredProto(ignored: Type) extends UncachedGroundType with MatchAlways { + override def revealIgnored = ignored.revealIgnored override def deepenProto(implicit ctx: Context): Type = ignored } @@ -202,7 +203,8 @@ object ProtoTypes { /** A prototype for selections in pattern constructors */ class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true) - trait ApplyingProto extends ProtoType + trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto + trait FunOrPolyProto extends ProtoType // common trait of PolyProto and FunProto class FunProtoState { @@ -227,7 +229,7 @@ object ProtoTypes { * [](args): resultType */ case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer, state: FunProtoState = new FunProtoState)(implicit ctx: Context) - extends UncachedGroundType with ApplyingProto { + extends UncachedGroundType with ApplyingProto with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = @@ -376,7 +378,15 @@ object ProtoTypes { override def resultType(implicit ctx: Context): Type = resType def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = - ctx.typer.isApplicable(tp, argType :: Nil, resultType) + ctx.typer.isApplicable(tp, argType :: Nil, resultType) || { + resType match { + case SelectionProto(name: TermName, mbrType, _, _) => + ctx.typer.hasExtensionMethod(tp, name, argType, mbrType) + //.reporting(res => i"has ext $tp $name $argType $mbrType: $res") + case _ => + false + } + } def derivedViewProto(argType: Type, resultType: Type)(implicit ctx: Context): ViewProto = if ((argType eq this.argType) && (resultType eq this.resultType)) this @@ -406,7 +416,7 @@ object ProtoTypes { * * [] [targs] resultType */ - case class PolyProto(targs: List[Type], resType: Type) extends UncachedGroundType with ProtoType { + case class PolyProto(targs: List[Tree], resType: Type) extends UncachedGroundType with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType @@ -418,17 +428,17 @@ object ProtoTypes { isInstantiatable(tp) || tp.member(nme.apply).hasAltWith(d => isInstantiatable(d.info)) } - def derivedPolyProto(targs: List[Type], resultType: Type): PolyProto = + def derivedPolyProto(targs: List[Tree], resultType: Type): PolyProto = if ((targs eq this.targs) && (resType eq this.resType)) this else PolyProto(targs, resType) override def notApplied: Type = WildcardType def map(tm: TypeMap)(implicit ctx: Context): PolyProto = - derivedPolyProto(targs mapConserve tm, tm(resultType)) + derivedPolyProto(targs, tm(resultType)) def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = - ta(ta.foldOver(x, targs), resultType) + ta(ta.foldOver(x, targs.tpes), resultType) override def deepenProto(implicit ctx: Context): PolyProto = derivedPolyProto(targs, resultType.deepenProto) } diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 4ca25185d907..1faa6d195cc5 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -49,7 +49,8 @@ trait TypeAssigner { def addRefinement(parent: Type, decl: Symbol) = { val inherited = - parentType.findMember(decl.name, cls.thisType, excluded = Private) + parentType.findMember(decl.name, cls.thisType, + required = EmptyFlagConjunction, excluded = Private) .suchThat(decl.matches(_)) val inheritedInfo = inherited.info if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a268497fae03..ebf8edc6ed2a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -107,32 +107,16 @@ class Typer extends Namer def newLikeThis: Typer = new Typer - /** Attribute an identifier consisting of a simple name or wildcard - * - * @param tree The tree representing the identifier. - * Transformations: (1) Prefix class members with this. - * (2) Change imported symbols to selections. - * (3) Change pattern Idents id (but not wildcards) to id @ _ + /** Find the type of an identifier with given `name` in given context `ctx`. + * @param name the name of the identifier + * @param pt the expected type + * @param required flags the result's symbol must have + * @param pos position to use for error reporting */ - def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = track("typedIdent") { + def findRef(name: Name, pt: Type, required: FlagConjunction, pos: Position)(implicit ctx: Context): Type = { val refctx = ctx - val name = tree.name val noImports = ctx.mode.is(Mode.InPackageClauseName) - /** Method is necessary because error messages need to bind to - * to typedIdent's context which is lost in nested calls to findRef - */ - def error(msg: => Message, pos: Position) = ctx.error(msg, pos) - - /** Does this identifier appear as a constructor of a pattern? */ - def isPatternConstr = - if (ctx.mode.isExpr && (ctx.outer.mode is Mode.Pattern)) - ctx.outer.tree match { - case Apply(`tree`, _) => true - case _ => false - } - else false - /** A symbol qualifies if it really exists and is not a package class. * In addition, if we are in a constructor of a pattern, we ignore all definitions * which are methods and not accessors (note: if we don't do that @@ -156,7 +140,7 @@ class Typer extends Namer * @param prevCtx The context of the previous denotation, * or else `NoContext` if nothing was found yet. */ - def findRef(previous: Type, prevPrec: Int, prevCtx: Context)(implicit ctx: Context): Type = { + def findRefRecur(previous: Type, prevPrec: Int, prevCtx: Context)(implicit ctx: Context): Type = { import BindingPrec._ /** Check that any previously found result from an inner context @@ -179,20 +163,20 @@ class Typer extends Namer } else { if (!scala2pkg && !previous.isError && !found.isError) { - error(AmbiguousImport(name, newPrec, prevPrec, prevCtx), tree.pos) + refctx.error(AmbiguousImport(name, newPrec, prevPrec, prevCtx), pos) } previous } def selection(imp: ImportInfo, name: Name) = if (imp.sym.isCompleting) { - ctx.warning(i"cyclic ${imp.sym}, ignored", tree.pos) + ctx.warning(i"cyclic ${imp.sym}, ignored", pos) NoType } else if (unimported.nonEmpty && unimported.contains(imp.site.termSymbol)) NoType else { val pre = imp.site - val denot = pre.member(name).accessibleFrom(pre)(refctx) + val denot = pre.memberBasedOnFlags(name, required, EmptyFlags).accessibleFrom(pre)(refctx) // Pass refctx so that any errors are reported in the context of the // reference instead of the if (reallyExists(denot)) pre.select(name, denot) else NoType @@ -208,8 +192,7 @@ class Typer extends Namer def checkUnambiguous(found: Type) = { val other = recur(selectors.tail) if (other.exists && found.exists && (found != other)) - error(em"reference to `$name` is ambiguous; it is imported twice in ${ctx.tree}", - tree.pos) + refctx.error(em"reference to `$name` is ambiguous; it is imported twice", pos) found } @@ -283,7 +266,7 @@ class Typer extends Namer else (ctx.scope ne lastCtx.scope) || (curOwner ne lastCtx.owner) if (isNewDefScope) { - val defDenot = ctx.denotNamed(name) + val defDenot = ctx.denotNamed(name, required) if (qualifies(defDenot)) { val found = if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType @@ -309,7 +292,7 @@ class Typer extends Namer if (defDenot.symbol is Package) result = checkNewOrShadowed(previous orElse found, packageClause) else if (prevPrec < packageClause) - result = findRef(found, packageClause, ctx)(ctx.outer) + result = findRefRecur(found, packageClause, ctx)(ctx.outer) } } } @@ -325,11 +308,11 @@ class Typer extends Namer else if (isPossibleImport(namedImport) && (curImport ne outer.importInfo)) { val namedImp = namedImportRef(curImport) if (namedImp.exists) - findRef(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) + findRefRecur(checkNewOrShadowed(namedImp, namedImport), namedImport, ctx)(outer) else if (isPossibleImport(wildImport) && !curImport.sym.isCompleting) { val wildImp = wildImportRef(curImport) if (wildImp.exists) - findRef(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) + findRefRecur(checkNewOrShadowed(wildImp, wildImport), wildImport, ctx)(outer) else { updateUnimported() loop(ctx)(outer) @@ -345,10 +328,23 @@ class Typer extends Namer } } - // begin findRef + // begin findRefRecur loop(NoContext)(ctx) } + findRefRecur(NoType, BindingPrec.nothingBound, NoContext) + } + + /** Attribute an identifier consisting of a simple name or wildcard + * + * @param tree The tree representing the identifier. + * Transformations: (1) Prefix class members with this. + * (2) Change imported symbols to selections. + * (3) Change pattern Idents id (but not wildcards) to id @ _ + */ + def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = track("typedIdent") { + val name = tree.name + // begin typedIdent def kind = if (name.isTermName) "" else "type " typr.println(s"typed ident $kind$name in ${ctx.owner}") @@ -365,7 +361,7 @@ class Typer extends Namer unimported = Set.empty foundUnderScala2 = NoType try { - var found = findRef(NoType, BindingPrec.nothingBound, NoContext) + var found = findRef(name, pt, EmptyFlagConjunction, tree.pos) if (foundUnderScala2.exists && !(foundUnderScala2 =:= found)) { ctx.migrationWarning( ex"""Name resolution will change. @@ -428,14 +424,20 @@ class Typer extends Namer def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { - def typeSelectOnTerm(implicit ctx: Context): Tree = { - val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) - if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) - val select = typedSelect(tree, pt, qual1) - if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) - else if (pt.isInstanceOf[PolyProto] || pt.isInstanceOf[FunProto] || pt == AssignProto) select - else typedDynamicSelect(tree, Nil, pt) - } + def typeSelectOnTerm(implicit ctx: Context): Tree = + typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) match { + case qual1 @ Applications.ExtMethodApply(app) => + pt.revealIgnored match { + case _: PolyProto => qual1 // keep the ExtMethodApply to strip at next typedTypeApply + case _ => app + } + case qual1 => + if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) + val select = typedSelect(tree, pt, qual1) + if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt)) + else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select + else typedDynamicSelect(tree, Nil, pt) + } def typeSelectOnType(qual: untpd.Tree)(implicit ctx: Context) = typedSelect(untpd.cpy.Select(tree)(qual, tree.name.toTypeName), pt) @@ -2093,10 +2095,9 @@ class Typer extends Namer } /** Is `pt` a prototype of an `apply` selection, or a parameterless function yielding one? */ - def isApplyProto(pt: Type)(implicit ctx: Context): Boolean = pt match { + def isApplyProto(pt: Type)(implicit ctx: Context): Boolean = pt.revealIgnored match { case pt: SelectionProto => pt.name == nme.apply case pt: FunProto => pt.args.isEmpty && isApplyProto(pt.resultType) - case pt: IgnoredProto => isApplyProto(pt.ignored) case _ => false } @@ -2145,7 +2146,8 @@ class Typer extends Namer def tryImplicit(fallBack: => Tree) = tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked).getOrElse(fallBack) - pt match { + if (ctx.mode.is(Mode.FixedQualifier)) tree + else pt match { case pt @ FunProto(Nil, _) if tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) && tree.getAttachment(DroppedEmptyArgs).isEmpty => @@ -2520,6 +2522,11 @@ class Typer extends Namer val ptNorm = underlyingApplied(pt) lazy val functionExpected = defn.isFunctionType(ptNorm) lazy val resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) + def needsEta = pt match { + case _: SingletonType => false + case IgnoredProto(_: FunOrPolyProto) => false + case _ => true + } wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) @@ -2531,7 +2538,7 @@ class Typer extends Namer // is tried. See strawman-contrib MapDecoratorTest.scala for an example where this happens. err.typeMismatch(tree, pt) } - case wtp: MethodType if !pt.isSingleton => + case wtp: MethodType if needsEta => val arity = if (functionExpected) if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none)) @@ -2592,6 +2599,28 @@ class Typer extends Namer } case _ => } + // try an extension method in scope + pt match { + case SelectionProto(name, mbrType, _, _) => + def tryExtension(implicit ctx: Context): Tree = + try { + findRef(name, WildcardType, ExtensionMethod, tree.pos) match { + case ref: TermRef => + extMethodApply(untpd.ref(ref).withPos(tree.pos), tree, mbrType) + case _ => EmptyTree + } + } + catch { + case ex: TypeError => errorTree(tree, ex.toMessage, tree.pos) + } + val nestedCtx = ctx.fresh.setNewTyperState() + val app = tryExtension(nestedCtx) + if (!app.isEmpty && !nestedCtx.reporter.hasErrors) { + nestedCtx.typerState.commit() + return Applications.ExtMethodApply(app).withType(app.tpe) + } + case _ => + } // try an implicit conversion val prevConstraint = ctx.typerState.constraint def recover(failure: SearchFailureType) = @@ -2600,6 +2629,8 @@ class Typer extends Namer else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType) inferView(tree, pt) match { + case SearchSuccess(inferred: Applications.ExtMethodApply, _, _) => + inferred // nothing to check or adapt for extension method applications case SearchSuccess(inferred, _, _) => checkImplicitConversionUseOK(inferred.symbol, tree.pos) readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled)) @@ -2677,7 +2708,10 @@ class Typer extends Namer case pt: FunProto => adaptToArgs(wtp, pt) case pt: PolyProto => - tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply + tree match { + case _: Applications.ExtMethodApply => tree + case _ => tryInsertApplyOrImplicit(tree, pt, locked)(tree) // error will be reported in typedTypeApply + } case _ => if (ctx.mode is Mode.Type) adaptType(tree.tpe) else adaptNoArgs(wtp) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index dd15c917d604..15824c4c74e9 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -248,7 +248,7 @@ class ReplDriver(settings: Array[String], val info = symbol.info val defs = info.bounds.hi.finalResultType - .membersBasedOnFlags(Method, Accessor | ParamAccessor | Synthetic | Private) + .membersBasedOnFlags(required = allOf(Method), excluded = Accessor | ParamAccessor | Synthetic | Private) .filterNot { denot => denot.symbol.owner == defn.AnyClass || denot.symbol.owner == defn.ObjectClass || diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index ec2b64d5e57f..74d55dc670d0 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -41,5 +41,6 @@ t6976 t7264 t7532b t8062 -typeclass-encoding2.scala typelevel0.scala +typeclass-encoding2.scala +typeclass-encoding3.scala diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index 91ab8c1a70a8..23420419ff39 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -1,5 +1,6 @@ Course-2002-07.scala eff-dependent.scala +extension-methods.scala i5257.scala lazy-implicit-lists.scala lazy-implicit-nums.scala diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 587a80b86bad..b51e93ab49d6 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -44,7 +44,7 @@ class DocASTPhase extends Phase { def membersFromSymbol(sym: Symbol): List[Entity] = { if (sym.info.exists) { - val defs = sym.info.bounds.hi.finalResultType.membersBasedOnFlags(Flags.Method, Flags.Synthetic | Flags.Private) + val defs = sym.info.bounds.hi.finalResultType.membersBasedOnFlags(Flags.allOf(Flags.Method), Flags.Synthetic | Flags.Private) .filterNot(_.symbol.owner.name.show == "Any") .map { meth => DefImpl( diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index d83734dc0854..06da1afd9f4c 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -278,7 +278,7 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` ### Bindings and Imports @@ -319,7 +319,8 @@ Dcl ::= RefineDcl ValDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree) DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree) -DefSig ::= id [DefTypeParamClause] DefParamClauses +DefSig ::= ‘(’ DefParam ‘)’ [nl] id + [DefTypeParamClause] DefParamClauses TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) | id [TypeParamClause] <: Type = MatchType diff --git a/docs/docs/reference/extension-methods.md b/docs/docs/reference/extension-methods.md new file mode 100644 index 000000000000..e91d879b4f33 --- /dev/null +++ b/docs/docs/reference/extension-methods.md @@ -0,0 +1,275 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +def (c: Circle) circumference: Double = c.radius * math.Pi * 2 +``` + +Like regular methods, extension methods can be invoked with infix `.`: + +```scala + val circle = Circle(0, 0, 1) + circle.circumference +``` + +### Translation of Extension Methods + +Extension methods are methods that have a parameter clause in front of the defined +identifier. They translate to methods where the leading parameter section is moved +to after the defined identifier. So, the definition of `circumference` above translates +to the plain method, and can also be invoked as such: +```scala +def circumference(c: Circle): Double = c.radius * math.Pi * 2 + +assert(circle.circumference == circumference(circle)) +``` + +### Translation of Calls to Extension Methods + +When is an extension method applicable? There are two possibilities. + + - An extension method is applicable if it is visible under a simple name, by being defined + or inherited or imported in a scope enclosing the application. + - An extension method is applicable if it is a member of an eligible implicit value at the point of the application. + +As an example, consider an extension method `longestStrings` on `String` defined in a trait `StringSeqOps`. + +```scala +trait StringSeqOps { + def (xs: Seq[String]) longestStrings = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +``` +We can make the extension method available by defining an implicit instance of `StringSeqOps`, like this: +```scala +implicit object ops1 extends StringSeqOps +``` +Then +```scala +List("here", "is", "a", "list").longestStrings +``` +is legal everywhere `StringSeqOps1` is available as an implicit value. Alternatively, we can define `longestStrings` +as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. + +```scala +object ops2 extends StringSeqOps +import ops2.longestStrings +List("here", "is", "a", "list").longestStrings +``` +The precise rules for resolving a selection to an extension method are as follows. + +Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, +and where `T` is the expected type. The following two rewritings are tried in order: + + 1. The selection is rewritten to `m[Ts](e)`. + 2. If the first rewriting does not typecheck with expected type `T`, and there is an implicit value `i` + in either the current scope or in the implicit scope of `T`, and `i` defines an extension + method named `m`, then selection is expanded to `i.m[Ts](e)`. + This second rewriting is attempted at the time where the compiler also tries an implicit conversion + from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. + +So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided +`circle` has type `Circle` and `CircleOps` is an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). + +### Operators + +The extension method syntax also applies to the definition of operators. +In each case the definition syntax mirrors the way the operator is applied. +Examples: +``` + def (x: String) < (y: String) = ... + def (x: Elem) +: (xs: Seq[Elem]) = ... + + "ab" + "c" + 1 +: List(2, 3) +``` +The two definitions above translate to +``` + def < (x: String)(y: String) = ... + def +: (xs: Seq[Elem])(x: Elem) = ... +``` +Note that swap of the two parameters `x` and `xs` when translating +the right-binding operator `+:` to an extension method. This is analogous +to the implementation of right binding operators as normal methods. + +### Generic Extensions + +The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method: + +```scala +def (xs: List[T]) second [T] = xs.tail.head +``` + +or: + + +```scala +def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) +``` + +or: + +```scala +def (x: T) + [T : Numeric](y: T): T = implicitly[Numeric[T]].plus(x, y) +``` + +As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. + +### A Larger Example + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) + + implicit object Ensuring { + def (x: T) ensuring [T](condition: implicit WrappedResult[T] => Boolean): T = { + implicit val wrapped = WrappedResult.wrap(x) + assert(condition) + x + } + } +} + +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 + } + +**A note on formatting** Having a parameter section in front of the defined +method name makes it visually harder to discern what is being defined. To address +that problem, it is recommended that the name of an extension method is +preceded by a space and is also followed by a space if there are more parameters +to come. + +### Extension Methods and TypeClasses + +The rules for expanding extension methods make sure that they work seamlessly with typeclasses. For instance, consider `SemiGroup` and `Monoid`. +```scala + // Two typeclasses: + trait SemiGroup[T] { + def (x: T) combine(y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + implicit object StringMonoid extends Monoid[String] { + def (x: String) combine (y: String): String = x.concat(y) + def unit: String = "" + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) +``` +In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, +which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit +evidence parameter summoned by the context bound `[T: Monoid]`. This works since +extension methods apply everywhere their enclosing object is available as an implicit. + +### Generic Extension Classes + +As another example, consider implementations of an `Ord` type class with a `minimum` value: +```scala + trait Ord[T] + def (x: T) compareTo (y: T): Int + def (x: T) < (y: T) = x.compareTo(y) < 0 + def (x: T) > (y: T) = x.compareTo(y) > 0 + val minimum: T + } + + implicit object IntOrd extends Ord[Int] { + def (x: Int) compareTo (y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue + } + + class ListOrd[T: Ord] extends Ord[List[T]] { + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + val minimum: List[T] = Nil + } + implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] + + + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) +``` + +### Higher Kinds + +Extension methods generalize to higher-kinded types without requiring special provisions. Example: + +```scala + trait Functor[F[_]] { + def (x: F[A]) map [A, B](f: A => B): F[B] + } + + trait Monad[F[_]] extends Functor[F] { + def (x: F[A]) flatMap [A, B](f: A => F[B]): F[B] + def (x: F[A]) map [A, B](f: A => B) = x.flatMap(f `andThen` pure) + + def pure[A](x: A): F[A] + } + + implicit object ListMonad extends Monad[List] { + def (xs: List[A]) flatMap [A, B](f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) + } + + implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { + def (r: Ctx => A) flatMap [A, B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + } +``` +### Syntax + +The required syntax extension just adds one clause for extension methods relative +to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). +``` +DefSig ::= ... + | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses +``` + + + + diff --git a/docs/sidebar.yml b/docs/sidebar.yml index eeb33c3e63c1..805b41359f2b 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -55,6 +55,8 @@ sidebar: url: docs/reference/tasty-reflect.html - title: Opaque Type Aliases url: docs/reference/opaques.html + - title: Extension Methods + url: docs/reference/extension-methods.html - title: By-Name Implicits url: docs/reference/implicit-by-name-parameters.html - title: Auto Parameter Tupling diff --git a/tests/neg/extension-methods.scala b/tests/neg/extension-methods.scala new file mode 100644 index 000000000000..a16fa8fee751 --- /dev/null +++ b/tests/neg/extension-methods.scala @@ -0,0 +1,14 @@ +object Test { + + implicit object O { + def (x: String) l1 = x.length + def l1(x: Int) = x * x + def l2(x: String) = x.length + } + + "".l1 // OK + "".l2 // error + 1.l1 // error + + +} \ No newline at end of file diff --git a/tests/pos/opaque-propability-xm.scala b/tests/pos/opaque-propability-xm.scala new file mode 100644 index 000000000000..2025994eacbb --- /dev/null +++ b/tests/pos/opaque-propability-xm.scala @@ -0,0 +1,41 @@ +object prob { + opaque type Probability = Double + + implicit object Probability { + def apply(n: Double): Option[Probability] = + if (0.0 <= n && n <= 1.0) Some(n) else None + + def unsafe(p: Double): Probability = { + require(0.0 <= p && p <= 1.0, s"probabilities lie in [0, 1] (got $p)") + p + } + + def asDouble(p: Probability): Double = p + + val Never: Probability = 0.0 + val CoinToss: Probability = 0.5 + val Certain: Probability = 1.0 + + implicit val ordering: Ordering[Probability] = + implicitly[Ordering[Double]] + + def (p1: Probability) unary_~ : Probability = Certain - p1 + def (p1: Probability) & (p2: Probability): Probability = p1 * p2 + def (p1: Probability) | (p2: Probability): Probability = p1 + p2 - (p1 * p2) + + def (p1: Probability) isImpossible: Boolean = p1 == Never + def (p1: Probability) isCertain: Boolean = p1 == Certain + + import scala.util.Random + + def (p1: Probability) sample (r: Random = Random): Boolean = r.nextDouble <= p1 + def (p1: Probability) toDouble: Double = p1 + } + + val caughtTrain = Probability.unsafe(0.3) + val missedTrain = ~caughtTrain + val caughtCab = Probability.CoinToss + val arrived = caughtTrain | (missedTrain & caughtCab) + + println((1 to 5).map(_ => arrived.sample()).toList) +} diff --git a/tests/pos/opaque-xm.scala b/tests/pos/opaque-xm.scala new file mode 100644 index 000000000000..2a936787219d --- /dev/null +++ b/tests/pos/opaque-xm.scala @@ -0,0 +1,35 @@ +import Predef.{any2stringadd => _, _} +object opaquetypes { + opaque type Logarithm = Double + + implicit 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 + + // This is the second way to unlift the logarithm type + def (x: Logarithm) toDouble: Double = math.exp(x) + def (x: Logarithm) + (y: Logarithm) = Logarithm(math.exp(x) + math.exp(y)) + def (x: Logarithm) * (y: Logarithm): Logarithm = Logarithm(x + y) + } +} +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 = Logarithm.toDouble(l3) + val l5: Logarithm = (1.0).asInstanceOf[Logarithm] +} diff --git a/tests/pos/typeclass-encoding3.scala b/tests/pos/typeclass-encoding3.scala new file mode 100644 index 000000000000..e3a9c5e5f4ab --- /dev/null +++ b/tests/pos/typeclass-encoding3.scala @@ -0,0 +1,403 @@ +object Test { + +/* -------------------------------------------------------------------------- + + Extension Methods & Simple Interfaces + + case class Reactangle(width: Double, height: Double) + case class Circle(radius: Double) + + extension for Rectangle + def area: Double = width * height + + trait HasArea + def area: Double + + extension for Circle : HasArea + def area = radius * 2 * math.Pi + +* ------------------------------------------------------------------------ */ + + case class Rectangle(width: Double, height: Double) + case class Circle(radius: Double) + + implicit class Rectangle_area(`this`: Rectangle) { + def area: Double = `this`.width * `this`.height + } + + implicit class Circle_HasArea(`this`: Circle) { + def area = `this`.radius * 2 * math.Pi + } + +/* -------------------------------------------------------------------------- + + Monomorphic: + + trait SemiGroup + def combine(that: This): This + common + type This + + trait Monoid extends SemiGroup + common + def unit: This + + class Str(val s: String) extends Monoid + def combine(that: Str): Str = new Str(this.s + that.s) + object Str: + def unit = "" + + extension for String : Monoid + def combine(that: String): String = this + that + common + def unit = "" + + def f[X: Monoid](x: X) = Monoid.by[X].unit.combine(x) + + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(Monoid.by[T].unit)(_ `combine` _) + +* ---------------------------------------------------------------------------- */ + + trait SemiGroup { + val common: SemiGroup.Common + import common._ + + def combine(that: This): This + } + object SemiGroup { + trait Common { self => + type This + def inject(x: This): SemiGroup { val common: self.type } + } + def by[A](implicit ev: SemiGroup.Common { type This = A }): ev.type = ev + } + + trait Monoid extends SemiGroup { + val common: Monoid.Common + import common._ + } + object Monoid { + trait Common extends SemiGroup.Common { self => + def inject(x: This): Monoid { val common: self.type } + def unit: This + } + def by[A](implicit ev: Monoid.Common { type This = A }): ev.type = ev + } + + class Str(val s: String) extends Monoid { + val common: Str.type = Str + def combine(that: Str): Str = new Str(this.s + that.s) + } + object Str extends Monoid.Common { + type This = Str + def inject(x: This): Str = x + def unit = new Str("") + } + + class SubStr(s: String) extends Str(s) + + implicit object String_Monoid extends Monoid.Common { self => + type This = String + class Impl(`this`: This) extends Monoid { + val common: self.type = self + def combine(that: String): String = `this` + that + } + def inject(x: This): Impl = new Impl(x) + def unit = "" + } + + def f[X](x: X)(implicit ev: Monoid.Common { type This = X }) = { + ev.inject(Monoid.by[X].unit).combine(x) + } + + def sum[A](xs: List[A])(implicit ev: Monoid.Common { type This = A }): A = + xs.foldLeft(Monoid.by[A].unit)(ev.inject(_).combine(_)) + + f(new Str("abc"))(Str) + f("abc") + f(new SubStr("abc"))(Str) + + sum(List("A", "B", "C")) + +/* -------------------------------------------------------------------------- + + Generic: + + trait Ord + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + common + val minimum: This + + extension for Int : Ord + def compareTo(that: Int) = + if (this < that) -1 else if (this > that) +1 else 0 + common + val minimum = Int.MinValue + + extension [T : Ord] for List[T] : Ord: + def compareTo(that: List[T]): Int = (this, that) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs.compareTo(ys) + common + val minimum = Nil + + def max[T: Ord](x: T, y: T) = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (Ord.by[T].minimum /: xs)(max(_, _)) + +* ---------------------------------------------------------------------------- */ + + trait Ord { + val common: Ord.Common + import common._ + + def compareTo(that: This): Int + def < (that: This) = compareTo(that) < 0 + def > (that: This) = compareTo(that) > 0 + } + object Ord { + trait Common { self => + type This + def inject(x: This): Ord { val common: self.type } + val minimum: This + } + def by[A](implicit ev: Ord.Common { type This = A }): ev.type = ev + } + + implicit object Int_Ord extends Ord.Common { self => + type This = Int + class Impl(`this`: This) extends Ord { + val common: self.type = self + def compareTo(that: Int) = + if (`this` < that) -1 else if (`this` > that) +1 else 0 + } + def inject(x: This): Impl = new Impl(x) + val minimum = Int.MinValue + } + + class List_Ord[T](implicit ev: Ord.Common { type This = T }) extends Ord.Common { self => + type This = List[T] + class Impl(`this`: This) extends Ord { + val common: self.type = self + def compareTo(that: List[T]): Int = (`this`, that) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs, y :: ys) => + val fst = ev.inject(x).compareTo(y) + if (fst != 0) fst else List_Ord[T].inject(xs).compareTo(ys) + } + } + def inject(x: This): Impl = new Impl(x) + val minimum = Nil + } + + implicit def List_Ord[T](implicit ev: Ord.Common { type This = T }): List_Ord[T] = + new List_Ord[T] + + def max[T](x: T, y: T)(implicit ev: Ord.Common { type This = T }) = if (ev.inject(x) < y) x else y + + def max[T](xs: List[T])(implicit ev: Ord.Common { type This = T }): T = + (Ord.by[T].minimum /: xs)(max(_, _)) + + val x1 = max(1, 2) + val x2 = max(List(1), Nil) + val x3 = max(List(1, 2, 3)) + val x4 = max(List(List(1, 2), List(3, 3), Nil)) + +/* -------------------------------------------------------------------------- + + Higher-kinded: + + trait Functor[A] + def map[B](f: A => B): This[B] + common + type This[A] + + trait Monad[A] extends Functor[A] + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B): This[B] = flatMap(f `andThen` pure) + def pure[A]: This[A] + + extension [A] for List[A] : Monad[A] + def flatMap[B](f: A => List[B]): List[B] = this.flatMap(f) + common + def pure[A](x: A) = x :: Nil + + def g[F: Functor, A, B](x: A, f: A => B): Functor.by[F].This[B] = + Functor.by[F].pure(x).map(f) + + def h[F: Monad, A, B](x: A): (A => Monad.by[F].This[B]) => Monad.by[F].This[B]) = + f => Monad.by[F].pure(x).flatMap(f) + +* ---------------------------------------------------------------------------- */ + + trait Functor[A] { + val common: Functor.Common + import common._ + + def map[B](f: A => B): This[B] + } + + object Functor { + trait Common { self => + type This[A] + def inject[A](x: This[A]): Functor[A] { val common: self.type } + } + inline def by[F[_]](implicit ev: Functor.Common { type This[A] = F[A] }): ev.type = ev + } + + trait Monad[A] extends Functor[A] { self => + val common: Monad.Common + import common._ + + def flatMap[B](f: A => This[B]): This[B] + def map[B](f: A => B): This[B] = flatMap(f `andThen` pure) + } + object Monad { + trait Common extends Functor.Common { self => + def pure[A](x: A): This[A] + def inject[A](x: This[A]): Monad[A] { val common: self.type } + } + def by[F[_]](implicit ev: Monad.Common { type This[A] = F[A] }): ev.type = ev + } + + implicit object List_Monad extends Monad.Common { self => + type This[A] = List[A] + class Impl[A](`this`: This[A]) extends Monad[A] { + val common: self.type = self + def flatMap[B](f: A => This[B]): This[B] = `this`.flatMap(f) + } + def pure[A](x: A): This[A] = x :: Nil + def inject[A](x: List[A]): Impl[A] = new Impl[A](x) + } + + def g[F[_], A, B](x: A, f: A => B)(implicit ev: Monad.Common { type This[A] = F[A] }) + : ev.This[B] = + ev.inject(Monad.by[F].pure(x)).map(f) + + def h[F[_], A, B](x: A)(implicit ev: Monad.Common { type This[A] = F[A] }) + : (A => ev.This[B]) => ev.This[B] = + f => ev.inject(Monad.by[F].pure(x)).flatMap(f) + + val r = g[F = List](1, _.toString) + +/* -------------------------------------------------------------------------- + + Generic and Higher-kinded: + + extension [A, Ctx] for Ctx => A : Monad[A] + def flatMap[B](f: A => Ctx => B): Ctx => B = + ctx => f(this(ctx))(ctx) + common + def pure[A](x: A) = ctx => x + +* ---------------------------------------------------------------------------- */ + + class $eq$gt_Monad[Ctx] extends Monad.Common { self => + type This[A] = Ctx => A + class Impl[A](`this`: This[A]) extends Monad[A] { + val common: self.type = self + def flatMap[B](f: A => This[B]): This[B] = + ctx => f(`this`(ctx))(ctx) + } + def pure[A](x: A): This[A] = + ctx => x + def inject[A](x: This[A]): Impl[A] = new Impl[A](x) + } + + implicit def $eq$gt_Monad[Ctx]: $eq$gt_Monad[Ctx] = new $eq$gt_Monad[Ctx] + + g[F = [X] => Int => X]((ctx: Int) => 1, x => (ctx: Int) => x.toString) + + +/* --------------------------------------------------------------------------------- + + case class Reactangle(width: Double, height: Double) + case class Circle(radius: Double) + + implicit object RectangleArea + def (x: Rectangle).area: Double = x.width * self.height + + trait HasArea[T] + def (x: T).area: Double + + implicit object CircleHasArea extends HasArea[Circle] + def (x: Circle).area = x.radius * 2 * math.Pi + + --------------------------------------------------------------------------------- + + trait SemiGroup[T] + def (x: T).combine(y: T): T + + trait Monoid[T] extends SemiGroup[T] + def unit: T + + def f[X: Monoid](x: X) = implicitly[Monoid[X]].unit.combine(x) + + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_ `combine` _) + + // Class `Str` is not definable + + implicit object StringMonoid extends Monoid[String] + def (x: String).combine(y: String): String = x + y + def unit = "" + + trait Ord[T] + def (x: T).compareTo(y: T): Int + def (x: T) < (that: T) = x.compareTo(y) < 0 + def (x: T) > (that: T) = x.compareTo(y) > 0 + val minimum: T + } + + implicit object IntOrd { + def (x: Int).compareTo(y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + } + + implicit class ListOrd[T: Ord] { + def (xs: List[T]).compareTo(ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + val minimum = Nil + } + + def max[T: Ord](x: T, y: T) = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = implicitly[Ord[T]].minimum /: xs)(max(_, _)) + + --------------------------------------------------------------------------------- + + trait Functor[F[_]] + def (x: F[A]).map[A, B](f: A => B): F[B] + + trait Monad[F[_]] extends Functor[F] + def (x: F[A]).flatMap[A, B](f: A => F[B]): F[B] + def (x: F[A]).map[A, B](f: A => B) = x.flatMap(f `andThen` pure) + def pure[A]: F[A] + + implicit object ListMonad extends Monad[List] + def (xs: List[A]).flatMap[A, B](f: A => List[B]): List[B] = xs.flatMap(f) + def pure[A]: List[A] = List.Nil + + implicit class ReaderMonad[Ctx] extends Monad[Ctx => _] + def (r: Ctx => A).flatMap[A, B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = ctx => x + + +*/ + +} \ No newline at end of file diff --git a/tests/run-separate-compilation/xml-interpolation-3/Test_2.scala b/tests/run-separate-compilation/xml-interpolation-3/Test_2.scala new file mode 100644 index 000000000000..9a3d8d64161b --- /dev/null +++ b/tests/run-separate-compilation/xml-interpolation-3/Test_2.scala @@ -0,0 +1,11 @@ +import XmlQuote._ + +object Test { + def main(args: Array[String]): Unit = { + + assert(xml"Hello Allan!" == Xml("Hello Allan!", Nil)) + + val name = new Object{} + assert(xml"Hello $name!" == Xml("Hello ??!", List(name))) + } +} diff --git a/tests/run-separate-compilation/xml-interpolation-3/XmlQuote_1.scala b/tests/run-separate-compilation/xml-interpolation-3/XmlQuote_1.scala new file mode 100644 index 000000000000..34402cd470b7 --- /dev/null +++ b/tests/run-separate-compilation/xml-interpolation-3/XmlQuote_1.scala @@ -0,0 +1,19 @@ +import scala.quoted._ +import scala.tasty.Reflection + +import scala.language.implicitConversions + +case class Xml(parts: String, args: List[Any]) + +object XmlQuote { + + implicit object SCOps { + inline def (inline ctx: StringContext) xml (args: => Any*): Xml = + ~XmlQuote.impl(ctx, '(args)) + } + + def impl(receiver: StringContext, args: Expr[Seq[Any]]): Expr[Xml] = { + val string = receiver.parts.mkString("??") + '(new Xml(~string.toExpr, (~args).toList)) + } +} diff --git a/tests/run/extension-methods.check b/tests/run/extension-methods.check new file mode 100644 index 000000000000..5cd13ae2676a --- /dev/null +++ b/tests/run/extension-methods.check @@ -0,0 +1,4 @@ +hihelloworld +-2147483648 +3 +List(2) diff --git a/tests/run/extension-methods.scala b/tests/run/extension-methods.scala new file mode 100644 index 000000000000..93df1c946483 --- /dev/null +++ b/tests/run/extension-methods.scala @@ -0,0 +1,117 @@ +object Test extends App { + + def (x: Int) em: Boolean = x > 0 + + assert(1.em == em(1)) + + case class Circle(x: Double, y: Double, radius: Double) + + def (c: Circle) circumference: Double = c.radius * math.Pi * 2 + + val circle = new Circle(1, 1, 2.0) + + assert(circle.circumference == circumference(circle)) + + def (xs: Seq[String]) longestStrings: Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } + val names = List("hi", "hello", "world") + assert(names.longestStrings == List("hello", "world")) + + def (xs: Seq[T]) second[T] = xs.tail.head + + assert(names.longestStrings.second == "world") + + def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) + + assert(List(names, List("!")).flattened == names :+ "!") + assert(Nil.flattened == Nil) + + trait SemiGroup[T] { + def (x: T) combine (y: T): T + } + trait Monoid[T] extends SemiGroup[T] { + def unit: T + } + + // An instance declaration: + implicit object StringMonoid extends Monoid[String] { + def (x: String) combine (y: String): String = x.concat(y) + def unit: String = "" + } + + // Abstracting over a typeclass with a context bound: + def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) + + println(sum(names)) + + trait Ord[T] { + def (x: T) compareTo (y: T): Int + def (x: T) < (y: T) = x.compareTo(y) < 0 + def (x: T) > (y: T) = x.compareTo(y) > 0 + val minimum: T + } + + implicit object IntOrd extends Ord[Int] { + def (x: Int) compareTo (y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + val minimum = Int.MinValue + } + + class ListOrd[T: Ord] extends Ord[List[T]] { + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = x.compareTo(y) + if (fst != 0) fst else xs1.compareTo(ys1) + } + val minimum: List[T] = Nil + } + implicit def listOrd[T: Ord]: ListOrd[T] = new ListOrd[T] + + def max[T: Ord](x: T, y: T): T = if (x < y) y else x + + def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) + + println(max(List[Int]())) + println(max(List(1, 2, 3))) + + println(max(List(1, 2, 3), List(2))) + + trait Functor[F[_]] { + def (x: F[A]) map [A, B](f: A => B): F[B] + } + + trait Monad[F[_]] extends Functor[F] { + def (x: F[A]) flatMap [A, B](f: A => F[B]): F[B] + def (x: F[A]) map [A, B](f: A => B) = x.flatMap(f `andThen` pure) + + def pure[A](x: A): F[A] + } + + implicit object ListMonad extends Monad[List] { + def (xs: List[A]) flatMap [A, B](f: A => List[B]): List[B] = + xs.flatMap(f) + def pure[A](x: A): List[A] = + List(x) + } + + class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { + def (r: Ctx => A) flatMap [A, B](f: A => Ctx => B): Ctx => B = + ctx => f(r(ctx))(ctx) + def pure[A](x: A): Ctx => A = + ctx => x + } + implicit def readerMonad[Ctx]: ReaderMonad[Ctx] = new ReaderMonad[Ctx] + + def mappAll[F[_]: Monad, T](x: T, fs: List[T => T]): F[T] = + fs.foldLeft(implicitly[Monad[F]].pure(x))((x: F[T], f: T => T) => + if (true) implicitly[Monad[F]].map(x)(f) + else if (true) x.map(f) + else x.map[T, T](f) + ) +} \ No newline at end of file diff --git a/tests/run/opaque-immutable-array-xm.scala b/tests/run/opaque-immutable-array-xm.scala new file mode 100644 index 000000000000..ffd07c59047e --- /dev/null +++ b/tests/run/opaque-immutable-array-xm.scala @@ -0,0 +1,51 @@ +import scala.reflect.ClassTag +object Test extends App { + + import java.util.Arrays + + opaque type IArray[A1] = Array[A1] + + implicit object IArray { + inline def initialize[A](body: => Array[A]): IArray[A] = body + def apply[A: ClassTag](xs: A*): IArray[A] = initialize(Array(xs: _*)) + + // These should be inline but that does not work currently. Try again + // once inliner is moved to ReifyQuotes + def (ia: IArray[A]) length[A]: Int = (ia: Array[A]).length + def (ia: IArray[A]) apply[A] (i: Int): A = (ia: Array[A])(i) + + // return a sorted copy of the array + def sorted[A <: AnyRef : math.Ordering](ia: IArray[A]): IArray[A] = { + val arr = Arrays.copyOf(ia, (ia: Array[A]).length) + scala.util.Sorting.quickSort(arr) + arr + } + + // use a standard java method to search a sorted IArray. + // (note that this doesn't mutate the array). + def binarySearch(ia: IArray[Long], elem: Long): Int = + Arrays.binarySearch(ia, elem) + } + + // same as IArray.binarySearch but implemented by-hand. + // + // given a sorted IArray, returns index of `elem`, + // or a negative value if not found. + def binaryIndexOf(ia: IArray[Long], elem: Long): Int = { + var lower: Int = 0 + var upper: Int = ia.length - 1 + while (lower <= upper) { + val middle = (lower + upper) >>> 1 + val n = ia(middle) + + if (n == elem) return middle + else if (n < elem) lower = middle + 1 + else upper = middle - 1 + } + -lower - 1 + } + + val xs: IArray[Long] = IArray(1L, 2L, 3L) + assert(binaryIndexOf(xs, 2L) == 1) + assert(binaryIndexOf(xs, 4L) < 0) +} \ No newline at end of file