Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New proposal for extension methods #5114

Merged
merged 56 commits into from Dec 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a708680
Establish companionship directly
odersky Oct 19, 2018
c78ee10
Compare two schemes how extensions could be used to encode typeclasses
odersky Sep 14, 2018
81a3cd5
New proposal for extension methods
odersky Sep 17, 2018
45ea7ae
Fix wording of translation rule
odersky Sep 17, 2018
04f4877
Fix example code
odersky Sep 17, 2018
e74fc4d
Address review comments
odersky Sep 17, 2018
dca503d
Another fix for example code
odersky Sep 17, 2018
54e6c85
Add missing type parameter
odersky Sep 17, 2018
75ea081
Add example file to pickling blacklist
odersky Sep 17, 2018
fdfe2f4
Parsing and pretty-printing of extension methods
odersky Sep 17, 2018
61b89c8
Fixes to code and syntax
odersky Sep 18, 2018
4b7121c
More fixes to doc
odersky Sep 18, 2018
85efcb4
Switch to prefix type arguments
odersky Sep 18, 2018
e88c3a8
Change syntax to "this modifier" scheme
odersky Sep 26, 2018
328140b
Parsing and pretty-printing for "this as modifier" scheme
odersky Sep 26, 2018
9b4dc40
Dotty docs for "this as modifier" scheme
odersky Sep 26, 2018
e3ad509
Fix rebase breakage
odersky Sep 26, 2018
93bedf5
Make extension objects eligible for implicit search
odersky Sep 27, 2018
6e224d8
Handle extension methods in implicit search
odersky Sep 27, 2018
e7c68ef
Avoid attachments to communicate extension method applications
odersky Sep 27, 2018
4481174
Avoid eta-expanding partially applied extension methods
odersky Sep 27, 2018
4a97662
Add check file to test
odersky Sep 27, 2018
18029a9
Add extensions-methods to pickling blacklist
odersky Sep 27, 2018
46f1a5c
Handle extension method applications with explicit type arguments
odersky Sep 28, 2018
d45fd68
Represent PolyProto arguments as trees, not types
odersky Sep 28, 2018
91729bc
Refactorings and polishings
odersky Sep 28, 2018
708504b
Consider extension methods if they are in scope
odersky Sep 29, 2018
c87a4a0
Catch cyclic references when trying extension methods
odersky Sep 29, 2018
6d0a157
Make findRef accessible outside of typedIdent
odersky Sep 30, 2018
1cf72c6
Add infrastructure to be able to search specifically for extension me…
odersky Sep 30, 2018
1a661db
Don't complete normal symbols when searching for an extension method
odersky Sep 30, 2018
6b31519
excluded -> exclusive
odersky Sep 30, 2018
601966f
Distinguish between required and excluded flags
odersky Sep 30, 2018
f25ec80
Fix rebase breakage
odersky Oct 12, 2018
981cb71
Update doc page to describe new resolution rules
odersky Oct 12, 2018
659e63f
Fix typo.
odersky Oct 12, 2018
c880d8e
Fixes for hasExtensionMethod
odersky Oct 14, 2018
5a4adf7
Clarify relationship to operators
odersky Oct 22, 2018
60aa019
Integrate extension methods docs in sidebar
odersky Oct 22, 2018
286a5d6
Update docs/docs/reference/extension-methods.md
buzden Oct 26, 2018
c19c240
Address review comments
odersky Oct 23, 2018
abddd42
Add disabled string interpolation test
odersky Nov 9, 2018
cb7a9d8
Revert: Dotty docs for "this as modifier" scheme
odersky Nov 17, 2018
f00cf7b
Revert: Parsing and pretty-printing for "this as modifier" scheme
odersky Nov 17, 2018
12cb7a1
Revert: Change syntax to "this modifier" scheme
odersky Nov 17, 2018
dd1db7a
Revert: Switch to prefix type arguments
odersky Nov 18, 2018
401931e
Extension methods with leading parameter list
odersky Nov 18, 2018
3d4366f
Handle positions in extension methods
odersky Nov 18, 2018
4c4c1e8
Update tests to use prefix parameter syntax for extension methods
odersky Nov 18, 2018
5f86ca0
Handle right-associative extension operators
odersky Nov 18, 2018
f97c6ff
Remove dead code in Parser
odersky Nov 21, 2018
7e2cd66
Avoid implicit objects that extend nothing
odersky Nov 26, 2018
cee2331
Implement reviewer suggestions
odersky Dec 4, 2018
9d7093c
Bring back xml-interpolation3 test
odersky Dec 4, 2018
3506192
Reject ambiguities between implicit conversions and extension methods
odersky Dec 4, 2018
49bae02
Reclassify test
odersky Dec 4, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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 => _}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
18 changes: 17 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Positioned.scala
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Expand Up @@ -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. */
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Printers.scala
Expand Up @@ -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
Expand Down
51 changes: 28 additions & 23 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Expand Up @@ -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 = _
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 = {
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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
}
Expand Down
21 changes: 19 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Expand Up @@ -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.
*/
Expand All @@ -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)
}

Expand Down Expand Up @@ -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`.
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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)

Expand Down Expand Up @@ -334,6 +345,9 @@ object Flags {
/** A method that has default params */
final val DefaultParameterized: FlagSet = termFlag(27, "<defaultparam>")

/** An extension method */
final val Extension = termFlag(28, "<extension>")

/** Symbol is defined by a Java class */
final val JavaDefined: FlagSet = commonFlag(30, "<java>")

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Expand Up @@ -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")
}
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Phases.scala
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) */
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Expand Up @@ -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))

Expand Down