diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c08111c99e09..5844b98b05fa 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -212,6 +212,8 @@ object desugar { * inline def f(x: Boolean): Any = if (x) 1 else "" * ==> * inline def f(x: Boolean): Any = (if (x) 1 else ""): Any + * + * 5. Wrap body of extension methods in { import this._ ; [] } */ private def defDef(meth0: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = { val meth @ DefDef(_, tparams, vparamss, tpt, rhs) = transformQuotedPatternName(meth0) @@ -287,12 +289,21 @@ object desugar { Nil } + val meth2 = + if (mods.is(Extension) && vparamss.exists(isThisParamClause(_)) && !meth1.rhs.isEmpty) + cpy.DefDef(meth1)( + rhs = Block( + Import(importDelegate = false, Ident(nme.this_), Ident(nme.WILDCARD) :: Nil) :: Nil, + meth1.rhs + )) + else meth1 + val defGetters = defaultGetters(vparamss, 0) - if (defGetters.isEmpty) meth1 + if (defGetters.isEmpty) meth2 else { - val meth2 = cpy.DefDef(meth1)(vparamss = normalizedVparamss) + val meth3 = cpy.DefDef(meth2)(vparamss = normalizedVparamss) .withMods(meth1.mods | DefaultParameterized) - Thicket(meth2 :: defGetters) + Thicket(meth3 :: defGetters) } } diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index a794bf7bae7e..96e65d5cb553 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -209,19 +209,31 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro 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) + if (tree.vparamss.exists(untpd.isThisParamClause)) { + check(tree.tparams) + tree.vparamss match { + case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) => + check(vparams2) + check(vparams1) + check(rest) + case _ => + check(tree.vparamss) + } } + else + 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 _ => diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 2c600701911a..5b161869bea5 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -143,6 +143,16 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => /** Is tree a backquoted identifier or definition */ def isBackquoted(tree: Tree): Boolean = tree.hasAttachment(Backquoted) + /** Is param of the form `this: T` ? */ + def isThisParam(param: ValDef): Boolean = + param.name == nme.this_ && !isBackquoted(param) + + /** Is params of the form `(this: T)` ? */ + def isThisParamClause(params: List[ValDef]): Boolean = params match { + case param :: Nil => isThisParam(param) + case _ => false + } + /** Is tree a variable pattern? */ def isVarPattern(pat: Tree): Boolean = unsplice(pat) match { case x: Ident => x.name.isVariableName && !isBackquoted(x) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3f4b8c8f19ef..f6193ac43a6c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2205,6 +2205,7 @@ object Parsers { /** ClsParamClause ::= [‘erased’] (‘(’ ClsParams ‘)’ * GivenClsParamClause::= ‘given’ [‘erased’] (‘(’ ClsParams ‘)’ | GivenTypes) + * ThisParamClause ::= [nl] ‘(’ {Annotation} ThisParam ‘)’ * ClsParams ::= ClsParam {‘,’ ClsParam} * ClsParam ::= {Annotation} * @@ -2212,6 +2213,7 @@ object Parsers { * GivenParamClause ::= ‘given’ [‘erased’] (‘(’ DefParams ‘)’ | GivenTypes) * DefParams ::= DefParam {‘,’ DefParam} * DefParam ::= {Annotation} [‘inline’] Param + * ThisParam ::= {Annotation} [‘inline’] ‘this’ ‘:’ ParamType * * Param ::= id `:' ParamType [`=' Expr] * @@ -2221,10 +2223,11 @@ object Parsers { 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 + thisOK: Boolean = false, // can be a this param clause initialMods: Modifiers = EmptyModifiers): List[ValDef] = { var impliedMods: Modifiers = initialMods - def param(): ValDef = { + def param(thisOK: Boolean): ValDef = { val start = in.offset var mods = impliedMods.withAnnotations(annotations()) if (ofClass) { @@ -2251,18 +2254,27 @@ object Parsers { mods |= Param } atSpan(start, nameStart) { - val name = ident() + val isBackquoted = in.token == BACKQUOTED_IDENT + val name = + if (thisOK && in.token == THIS) { + in.nextToken() + nme.this_ + } + else ident() accept(COLON) if (in.token == ARROW && ofClass && !mods.is(Local)) syntaxError(VarValParametersMayNotBeCallByName(name, mods.is(Mutable))) val tpt = paramType() + def isThisParam = name == nme.this_ && !isBackquoted val default = - if (in.token == EQUALS) { in.nextToken(); expr() } + if (in.token == EQUALS && !isThisParam) { in.nextToken(); expr() } else EmptyTree if (impliedMods.mods.nonEmpty) { impliedMods = impliedMods.withMods(Nil) // keep only flags, so that parameter positions don't overlap } - ValDef(name, tpt, default).withMods(mods) + val result = ValDef(name, tpt, default).withMods(mods) + if (isBackquoted) result.pushAttachment(Backquoted, ()) + result } } @@ -2284,9 +2296,19 @@ object Parsers { else { if (in.token == IMPLICIT && !impliedMods.isOneOf(Given | Erased)) impliedMods = addMod(impliedMods, atSpan(accept(IMPLICIT)) { Mod.Implicit() }) - val clause = - if (prefix) param() :: Nil - else commaSeparated(() => param()) + val clause = { + val leading = param(thisOK) + if (prefix || isThisParam(leading)) + leading :: Nil + else { + val ts = new ListBuffer[ValDef] += leading + while (in.token == COMMA) { + in.nextToken() + ts += param(thisOK = false) + } + ts.toList + } + } checkVarArgsRules(clause) clause } @@ -2302,8 +2324,9 @@ object Parsers { */ def paramClauses(ofClass: Boolean = false, ofCaseClass: Boolean = false, - ofInstance: Boolean = false): List[List[ValDef]] = { - def recur(firstClause: Boolean, nparams: Int, contextualOnly: Boolean): List[List[ValDef]] = { + ofInstance: Boolean = false, + thisOK: Boolean = false): List[List[ValDef]] = { + def recur(firstClause: Boolean, nparams: Int, givenOnly: Boolean): List[List[ValDef]] = { var initialMods = EmptyModifiers if (in.token == GIVEN) { in.nextToken() @@ -2313,10 +2336,10 @@ object Parsers { in.nextToken() initialMods |= Erased } - val isContextual = initialMods.is(Given) + val isGiven = initialMods.is(Given) newLineOptWhenFollowedBy(LPAREN) def isParamClause: Boolean = - !isContextual || { + !isGiven || { val lookahead = in.lookaheadScanner lookahead.nextToken() paramIntroTokens.contains(lookahead.token) && { @@ -2328,28 +2351,29 @@ object Parsers { } } if (in.token == LPAREN && isParamClause) { - if (contextualOnly && !isContextual) + if (givenOnly && !isGiven) if (ofInstance) syntaxError(em"parameters of instance definitions must come after `given'") else syntaxError(em"normal parameters cannot come after `given' clauses") val params = paramClause( ofClass = ofClass, ofCaseClass = ofCaseClass, firstClause = firstClause, + thisOK = thisOK && firstClause, initialMods = initialMods) val lastClause = params.nonEmpty && params.head.mods.flags.is(Implicit) - params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length, isContextual)) + params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length, isGiven)) } - else if (isContextual) { + else if (isGiven) { val tps = commaSeparated(() => annotType()) var counter = nparams def nextIdx = { counter += 1; counter } val paramFlags = if (ofClass) Private | Local | ParamAccessor else Param val params = tps.map(makeSyntheticParameter(nextIdx, _, paramFlags | Synthetic | Given)) - params :: recur(firstClause = false, nparams + params.length, isContextual) + params :: recur(firstClause = false, nparams + params.length, isGiven) } else Nil } - recur(firstClause = true, 0, ofInstance) + recur(firstClause = true, nparams = 0, givenOnly = ofInstance) } /* -------- DEFS ------------------------------------------- */ @@ -2538,6 +2562,7 @@ object Parsers { * | this ParamClause ParamClauses `=' ConstrExpr * DefDcl ::= DefSig `:' Type * DefSig ::= [‘(’ DefParam ‘)’ [nl]] id [DefTypeParamClause] ParamClauses + * | id [DefTypeParamClause] [ThisParamClause] DefParamClauses */ def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atSpan(start, nameStart) { def scala2ProcedureSyntax(resultTypeStr: String) = { @@ -2565,20 +2590,26 @@ object Parsers { } makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start)) } else { - val (leadingParamss, flags) = + val leadingParamss = if (in.token == LPAREN) - try (paramClause(prefix = true) :: Nil, Method | Extension) + try paramClause(prefix = true) :: Nil finally newLineOpt() - else - (Nil, Method) - val mods1 = addFlag(mods, flags) + else Nil + var mods1 = addFlag(mods, Method) val ident = termIdent() val tparams = typeParamClauseOpt(ParamOwner.Def) - val vparamss = paramClauses() match { - case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(ident.name) => - rparams :: leadingParamss ::: rparamss - case rparamss => - leadingParamss ::: rparamss + val vparamssAsGiven = leadingParamss ::: paramClauses(thisOK = leadingParamss.isEmpty) + vparamssAsGiven match { + case (vparam :: Nil) :: _ if leadingParamss.nonEmpty || isThisParam(vparam) => + mods1 = addFlag(mods, Extension) + case _ => + } + // swap first two parameter lists of right-associative extension operators + val vparamss = vparamssAsGiven match { + case rparams1 :: rparams2 :: rparamss if mods1.is(Extension) && !isLeftAssoc(ident.name) => + rparams2 :: rparams1 :: rparamss + case _ => + vparamssAsGiven } var tpt = fromWithinReturnType { if (in.token == SUBTYPE && mods.is(Inline)) { diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 551429b3b959..3bcbe7d40801 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -491,7 +491,7 @@ object Erasure { checkNotErased(recur(qual1)) } - override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = + override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree) else { ctx.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}") diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 4f61563d4e31..7ec0624a8b4c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -341,8 +341,8 @@ class TreeChecker extends Phase with SymTransformer { checkNotRepeated(super.typedSelect(tree, pt)) } - override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = { - val res = super.typedThis(tree) + override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = { + val res = super.typedThis(tree, pt) val cls = res.symbol assert(cls.isStaticOwner || ctx.owner.isContainedIn(cls), i"error while typing $tree, ${ctx.owner} is not contained in $cls") res diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index bce41cdbce92..94d042fbf67f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -45,7 +45,7 @@ class ReTyper extends Typer with ReChecking { override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Tree = promote(tree) - override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = + override def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = promote(tree) override def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e48b380fda7f..6aa1fc34e833 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -195,7 +195,7 @@ class Typer extends Namer // 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 + if (qualifies(denot)) pre.select(name, denot) else NoType } /** The type representing a named import with enclosing name when imported @@ -491,8 +491,11 @@ class Typer extends Namer typeSelectOnTerm(ctx) } - def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = track("typedThis") { - assignType(tree) + def typedThis(tree: untpd.This, pt: Type)(implicit ctx: Context): Tree = track("typedThis") { + def isExtensionThis(owner: Symbol): Boolean = + owner.is(Extension) || owner.isTerm && isExtensionThis(owner.owner) + if (isExtensionThis(ctx.owner) && tree.qual.isEmpty) typedIdent(cpy.Ident(tree)(nme.this_), pt) + else assignType(tree) } def typedSuper(tree: untpd.Super, pt: Type)(implicit ctx: Context): Tree = track("typedSuper") { @@ -2145,7 +2148,7 @@ class Typer extends Namer def typedUnnamed(tree: untpd.Tree): Tree = tree match { case tree: untpd.Apply => if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) else typedApply(tree, pt) - case tree: untpd.This => typedThis(tree) + case tree: untpd.This => typedThis(tree, pt) case tree: untpd.Literal => typedLiteral(tree) case tree: untpd.New => typedNew(tree, pt) case tree: untpd.Typed => typedTyped(tree, pt) diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index cc28d2deff2f..97907812168a 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -147,8 +147,8 @@ class ReplCompilerTests extends ReplTest { run(""" |trait Ord[T] { | def compare(x: T, y: T): Int - | def (x: T) < (y: T) = compare(x, y) < 0 - | def (x: T) > (y: T) = compare(x, y) > 0 + | def < (this: T)(that: T) = compare(this, that) < 0 + | def > (this: T)(that: T) = compare(this, that) > 0 |} | |delegate IntOrd for Ord[Int] { diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 8ffd972129e3..67fe06fd4c86 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -310,8 +310,10 @@ DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [‘implicit’] DefParams | {DefParamClause} {GivenParamClause} DefParamClause ::= ‘(’ DefParams ‘)’ GivenParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | GivenTypes) +ThisParamClause ::= ‘(’ ThisParam ‘)’ DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +ThisParam ::= {Annotation} [‘inline’] ‘this’ ‘:’ ParamType GivenTypes ::= AnnotType {‘,’ AnnotType} ClosureMods ::= { ‘implicit’ | ‘given’} ``` @@ -357,8 +359,7 @@ 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 ::= [‘(’ DefParam ‘)’ [nl]] id - [DefTypeParamClause] DefParamClauses +DefSig ::= id [DefTypeParamClause] [ThisParamClause] DefParamClauses TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound Def ::= ‘val’ PatDef diff --git a/docs/docs/reference/changed-features/operators.md b/docs/docs/reference/changed-features/operators.md index 0a618e0426a3..947580492e5a 100644 --- a/docs/docs/reference/changed-features/operators.md +++ b/docs/docs/reference/changed-features/operators.md @@ -113,8 +113,8 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b @infix def op[T](x: T)(y: S): R // ok @infix def op[T](x: T, y: S): R // error: two parameters - @infix def (x: A) op (y: B): R // ok - @infix def (x: A) op (y1: B, y2: B): R // error: two parameters + @infix def op(this: A)(y: B): R // ok + @infix def op(this: A)(y1: B, y2: B): R // error: two parameters ``` 4. @infix annotations can also be given to type, trait or class definitions that have exactly two type parameters. An infix type like diff --git a/docs/docs/reference/contextual/delegates.md b/docs/docs/reference/contextual/delegates.md index 60562b5be8de..b81563956bd1 100644 --- a/docs/docs/reference/contextual/delegates.md +++ b/docs/docs/reference/contextual/delegates.md @@ -9,8 +9,8 @@ that serve for synthesizing arguments to [given clauses](./given-clauses.html). ```scala trait Ord[T] { def compare(x: T, y: T): Int - def (x: T) < (y: T) = compare(x, y) < 0 - def (x: T) > (y: T) = compare(x, y) > 0 + def < (this: T)(that: T) = compare(this, that) < 0 + def > (this: T)(that: T) = compare(this, that) > 0 } delegate IntOrd for Ord[Int] { diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index ee8ac4d91356..2f414f9e6bfb 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -8,7 +8,7 @@ Extension methods allow one to add methods to a type after the type is defined. ```scala case class Circle(x: Double, y: Double, radius: Double) -def (c: Circle) circumference: Double = c.radius * math.Pi * 2 +def circumference(this: Circle): Double = this.radius * math.Pi * 2 ``` Like regular methods, extension methods can be invoked with infix `.`: @@ -20,13 +20,9 @@ Like regular methods, extension methods can be invoked with infix `.`: ### 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: +Extension methods are methods that have a `this` parameter as first parameter clause. They translate to +normal methods, that can also be invoked as usual: ```scala -def circumference(c: Circle): Double = c.radius * math.Pi * 2 - assert(circle.circumference == circumference(circle)) ``` @@ -42,9 +38,9 @@ As an example, consider an extension method `longestStrings` on `String` defined ```scala trait StringSeqOps { - def (xs: Seq[String]) longestStrings = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) + def longestStrings(this: Seq[String]) = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) } } ``` @@ -84,19 +80,22 @@ Delegates that define extension methods can also be defined without a `for` clau ```scala delegate StringOps { - def (xs: Seq[String]) longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) + def longestStrings(this: Seq[String]) : Seq[String] = { + val maxLength = this.map(_.length).max + this.filter(_.length == maxLength) } } delegate { - def (xs: List[T]) second[T] = xs.tail.head + def second[T](this: List[T]) = this.tail.head } ``` If such delegates are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method. +### Meaning of This + +Inside an extension method, ... TODO: fill in ### Operators The extension method syntax also applies to the definition of operators. diff --git a/tests/neg/i4453.scala b/tests/neg/i4453.scala index cc339f83b35b..b8a60e1ed2ee 100644 --- a/tests/neg/i4453.scala +++ b/tests/neg/i4453.scala @@ -1,2 +1,2 @@ -class x0 { var x0 == _ * // error: _* can be used only for last argument // error: == cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method +class x0 { var x0 == _ * // error: _* can be used only for last argument // error '=' expected, but eof found \ No newline at end of file diff --git a/tests/pos/Flags1.scala b/tests/pos/Flags1.scala new file mode 100644 index 000000000000..39ecbccd62d9 --- /dev/null +++ b/tests/pos/Flags1.scala @@ -0,0 +1,376 @@ +import language.implicitConversions + +object Flags { + + object opaques { + + /** A FlagSet represents a set of flags. Flags are encoded as follows: + * The first two bits indicate whether a flag set applies to terms, + * to types, or to both. Bits 2..63 are available for properties + * and can be doubly used for terms and types. + */ + opaque type FlagSet = Long + def FlagSet(bits: Long): FlagSet = bits + def toBits(fs: FlagSet): Long = fs + + /** A flag set consisting of a single flag */ + opaque type Flag <: FlagSet = Long + private[Flags] def Flag(bits: Long): Flag = bits + } + export opaques.FlagSet + + type Flag = opaques.Flag + + delegate FlagOps { + + def (x: FlagSet) + bits: Long = opaques.toBits(x) + + /** The union of the given flag sets. + * Combining two FlagSets with `|` will give a FlagSet + * that has the intersection of the applicability to terms/types + * of the two flag sets. It is checked that the intersection is not empty. + */ + def (x: FlagSet) + | (y: FlagSet): FlagSet = + if (x.bits == 0) y + else if (y.bits == 0) x + else { + val tbits = x.bits & y.bits & KINDFLAGS + if (tbits == 0) + assert(false, s"illegal flagset combination: $x and $y") + FlagSet(tbits | ((x.bits | y.bits) & ~KINDFLAGS)) + } + + /** The intersection of the given flag sets */ + def (x: FlagSet) + & (y: FlagSet): FlagSet = FlagSet(x.bits & y.bits) + + /** The intersection of a flag set with the complement of another flag set */ + def (x: FlagSet) + &~ (y: FlagSet): FlagSet = { + val tbits = x.bits & KINDFLAGS + if ((tbits & y.bits) == 0) x + else FlagSet(tbits | ((x.bits & ~y.bits) & ~KINDFLAGS)) + } + + def (x: FlagSet) + ^ (y: FlagSet) = + FlagSet((x.bits | y.bits) & KINDFLAGS | (x.bits ^ y.bits) & ~KINDFLAGS) + + /** Does the given flag set contain the given flag? + * This means that both the kind flags and the carrier bits have non-empty intersection. + */ + def (x: FlagSet) + is(flag: Flag): Boolean = { + val fs = x.bits & flag.bits + (fs & KINDFLAGS) != 0 && (fs & ~KINDFLAGS) != 0 + } + + /** Does the given flag set contain the given flag + * and at the same time contain none of the flags in the `butNot` set? + */ + def (x: FlagSet) + is(flag: Flag, butNot: FlagSet): Boolean = x.is(flag) && !x.isOneOf(butNot) + + /** Does the given flag set have a non-empty intersection with another flag set? + * This means that both the kind flags and the carrier bits have non-empty intersection. + */ + def (x: FlagSet) + isOneOf(flags: FlagSet): Boolean = { + val fs = x.bits & flags.bits + (fs & KINDFLAGS) != 0 && (fs & ~KINDFLAGS) != 0 + } + + /** Does the given flag set have a non-empty intersection with another flag set, + * and at the same time contain none of the flags in the `butNot` set? + */ + def (x: FlagSet) + isOneOf(flags: FlagSet, butNot: FlagSet): Boolean = x.isOneOf(flags) && !x.isOneOf(butNot) + + /** Does a given flag set have all of the flags of another flag set? + * Pre: The intersection of the term/type flags of both sets must be non-empty. + */ + def (x: FlagSet) + isAllOf(flags: FlagSet): Boolean = { + val fs = x.bits & flags.bits + ((fs & KINDFLAGS) != 0 || flags.bits == 0) && + (fs >>> TYPESHIFT) == (flags.bits >>> TYPESHIFT) + } + + /** Does a given flag set have all of the flags in another flag set + * and at the same time contain none of the flags in the `butNot` set? + * Pre: The intersection of the term/type flags of both sets must be non-empty. + */ + def (x: FlagSet) + isAllOf(flags: FlagSet, butNot: FlagSet): Boolean = x.isAllOf(flags) && !x.isOneOf(butNot) + + def (x: FlagSet) + isEmpty: Boolean = (x.bits & ~KINDFLAGS) == 0 + + /** Is a given flag set a subset of another flag set? */ + def (x: FlagSet) + <= (y: FlagSet): Boolean = (x.bits & y.bits) == x.bits + + /** Does the given flag set apply to terms? */ + def (x: FlagSet) + isTermFlags: Boolean = (x.bits & TERMS) != 0 + + /** Does the given flag set apply to terms? */ + def (x: FlagSet) + isTypeFlags: Boolean = (x.bits & TYPES) != 0 + + /** The given flag set with all flags transposed to be type flags */ + def (x: FlagSet) + toTypeFlags: FlagSet = if (x.bits == 0) x else FlagSet(x.bits & ~KINDFLAGS | TYPES) + + /** The given flag set with all flags transposed to be term flags */ + def (x: FlagSet) + toTermFlags: FlagSet = if (x.bits == 0) x else FlagSet(x.bits & ~KINDFLAGS | TERMS) + + /** The given flag set with all flags transposed to be common flags */ + def (x: FlagSet) + toCommonFlags: FlagSet = if (x.bits == 0) x else FlagSet(x.bits | KINDFLAGS) + + /** The number of non-kind flags in the given flag set */ + def (x: FlagSet) + numFlags: Int = java.lang.Long.bitCount(x.bits & ~KINDFLAGS) + + /** The lowest non-kind bit set in the given flag set */ + def (x: FlagSet) + firstBit: Int = java.lang.Long.numberOfTrailingZeros(x.bits & ~KINDFLAGS) + + /** The list of non-empty names of flags with given index idx that are set in the given flag set */ + private def (x: FlagSet) + flagString(idx: Int): List[String] = + if ((x.bits & (1L << idx)) == 0) Nil + else { + def halfString(kind: Int) = + if ((x.bits & (1L << kind)) != 0) flagName(idx)(kind) else "" + val termFS = halfString(TERMindex) + val typeFS = halfString(TYPEindex) + val strs = termFS :: (if (termFS == typeFS) Nil else typeFS :: Nil) + strs filter (_.nonEmpty) + } + + /** The list of non-empty names of flags that are set in the given flag set */ + def (x: FlagSet) + flagStrings(privateWithin: String): Seq[String] = { + var rawStrings = (2 to MaxFlag).flatMap(x.flagString(_)) // DOTTY problem: cannot drop with (_) + if (!privateWithin.isEmpty && !x.is(Protected)) + rawStrings = rawStrings :+ "private" + val scopeStr = if (x.is(Local)) "this" else privateWithin + if (scopeStr != "") + rawStrings.filter(_ != "").map { + case "private" => s"private[$scopeStr]" + case "protected" => s"protected[$scopeStr]" + case str => str + } + else rawStrings + } + + /** The string representation of the given flag set */ + def (x: FlagSet) + flagsString: String = x.flagStrings("").mkString(" ") + } + + def termFlagSet(x: Long) = FlagSet(TERMS | x) + + private inline val TYPESHIFT = 2 + private inline val TERMindex = 0 + private inline val TYPEindex = 1 + private inline val TERMS = 1 << TERMindex + private inline val TYPES = 1 << TYPEindex + private inline val KINDFLAGS = TERMS | TYPES + + private inline val FirstFlag = 2 + private inline val FirstNotPickledFlag = 48 + private inline val MaxFlag = 63 + + private val flagName = Array.fill(64, 2)("") + + private def isDefinedAsFlag(idx: Int) = flagName(idx).exists(_.nonEmpty) + + /** The flag set containing all defined flags of either kind whose bits + * lie in the given range + */ + private def flagRange(start: Int, end: Int) = + FlagSet((KINDFLAGS.toLong /: (start until end)) ((bits, idx) => + if (isDefinedAsFlag(idx)) bits | (1L << idx) else bits)) + + /** The union of all flags in given flag set */ + def union(flagss: FlagSet*): FlagSet = { + var flag = EmptyFlags + for (f <- flagss) + flag |= f + flag + } + + def commonFlags(flagss: FlagSet*): FlagSet = union(flagss.map(_.toCommonFlags): _*) + + /** The empty flag set */ + val EmptyFlags: FlagSet = FlagSet(0) + + /** The undefined flag set */ + val UndefinedFlags: FlagSet = FlagSet(~KINDFLAGS) + + /** Three flags with given index between 2 and 63. + * The first applies to both terms and types. the second is a term flag, and + * the third is a type flag. Installs given name(s) as the name(s) of the flags. + * @param name The name to be used for the term flag + * @param typeName The name to be used for the type flag, if it is different from `name`. + */ + private def newFlags(index: Int, name: String, typeName: String = ""): (Flag, Flag, Flag) = { + flagName(index)(TERMindex) = name + flagName(index)(TYPEindex) = if (typeName.isEmpty) name else typeName + val bits = 1L << index + (opaques.Flag(KINDFLAGS | bits), opaques.Flag(TERMS | bits), opaques.Flag(TYPES | bits)) + } + + // ----------------- Available flags ----------------------------------------------------- + + /** Labeled with `private` modifier */ + val (Private @ _, PrivateTerm @ _, PrivateType @ _) = newFlags(2, "private") + + /** Labeled with `protected` modifier */ + val (Protected @ _, _, _) = newFlags(3, "protected") + + /** Labeled with `override` modifier */ + val (Override @ _, _, _) = newFlags(4, "override") + + /** A declared, but not defined member */ + val (Deferred @ _, DeferredTerm @ _, DeferredType @ _) = newFlags(5, "") + + /** Labeled with `final` modifier */ + val (Final @ _, _, _) = newFlags(6, "final") + + /** A method symbol */ + val (_, Method @ _, HigherKinded @ _) = newFlags(7, "", "") // TODO drop HigherKinded + + /** A (term or type) parameter to a class or method */ + val (Param @ _, TermParam @ _, TypeParam @ _) = newFlags(8, "") + + /** Labeled with `implicit` modifier (implicit value) */ + val (Implicit @ _, ImplicitTerm @ _, _) = newFlags(9, "implicit") + + /** Labeled with `lazy` (a lazy val) / a trait */ + val (LazyOrTrait @ _, Lazy @ _, Trait @ _) = newFlags(10, "lazy", "") + + /** A value or variable accessor (getter or setter) */ + val (AccessorOrSealed @ _, Accessor @ _, Sealed @ _) = newFlags(11, "", "sealed") + + /** A mutable var */ + val (_, Mutable @ _, _) = newFlags(12, "mutable") + + /** Symbol is local to current class (i.e. private[this] or protected[this] + * pre: Private or Protected are also set + */ + val (Local @ _, _, _) = newFlags(13, "") + + /** A field generated for a primary constructor parameter (no matter if it's a 'val' or not), + * or an accessor of such a field. + */ + val (_, ParamAccessor @ _, _) = newFlags(14, "") + + /** A value or class implementing a module */ + val (Module @ _, ModuleVal @ _, ModuleClass @ _) = newFlags(15, "module") + + /** A value or class representing a package */ + val (Package @ _, PackageVal @ _, PackageClass @ _) = newFlags(16, "") + + /** A case class or its companion object + * Note: Case is also used to indicate that a symbol is bound by a pattern. + */ + val (Case @ _, CaseVal @ _, CaseClass @ _) = newFlags(17, "case") + + /** A compiler-generated symbol, which is visible for type-checking + * (compare with artifact) + */ + val (Synthetic @ _, _, _) = newFlags(18, "") + + /** Labelled with `inline` modifier */ + val (Inline @ _, _, _) = newFlags(19, "inline") + + /** An outer accessor / a covariant type variable */ + val (OuterOrCovariant @ _, OuterAccessor @ _, Covariant @ _) = newFlags(20, "", "") + + /** The label of a labeled block / a contravariant type variable */ + val (LabelOrContravariant @ _, Label @ _, Contravariant @ _) = newFlags(21, "