Skip to content

An Alternative to Implicits #5458

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

Merged
merged 55 commits into from
Jan 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
7111961
Parser polishings
odersky Dec 10, 2018
b1fe483
Implement witnesses
odersky Oct 17, 2018
adf55ef
Implement anonymous witnesses
odersky Oct 17, 2018
8539daa
Fix tests
odersky Oct 17, 2018
2dc6c08
WIP Witness parameter syntax
odersky Oct 23, 2018
c2c843f
Complete reference documentation
odersky Oct 25, 2018
ac28cc9
Some more details in docs
odersky Oct 26, 2018
ca24bc4
Add witness as a modifier
odersky Oct 29, 2018
6d97a2c
Add witness as a modifier
odersky Oct 29, 2018
73cd252
Reworked proposal
odersky Oct 31, 2018
ae3f613
Split out abstract and alias witnesses into separate proposal
odersky Oct 31, 2018
16abc13
Parsing of contextual (`with`) parameters and arguments
odersky Nov 11, 2018
e672c0c
Introduce contextual method types
odersky Nov 11, 2018
ddc8dc7
Type checking of contextual applications
odersky Nov 12, 2018
9c0b203
Changes to implicit function types
odersky Nov 12, 2018
da97c7f
Fix dependent function type building
odersky Nov 13, 2018
593660c
Implement and test "replacing-implicits" chapter.
odersky Nov 16, 2018
4510f71
Flesh out migration discussion
odersky Nov 16, 2018
319dac7
Blacklist fromTasty test
odersky Nov 16, 2018
45ac765
Fix IDE to use new IFT syntax
odersky Nov 16, 2018
7c38257
Revert alternative infix syntax for extension methods
odersky Nov 16, 2018
c89be2b
Drop dependency injection section
odersky Nov 17, 2018
03d3dac
Tweaks
odersky Nov 17, 2018
1b879a2
Add disabled string interpolation test
odersky Nov 9, 2018
2b357c7
Revert: Dotty docs for "this as modifier" scheme
odersky Dec 10, 2018
9b55656
Handle right-associative extension operators
odersky Nov 18, 2018
15571f9
Support witness defs with () parameters
odersky Nov 20, 2018
c06f66e
Use `of` instead of `for` in witness definitions
odersky Nov 20, 2018
5522497
Remove dead code in Parser
odersky Nov 21, 2018
4cf5dc3
Fix typo
odersky Dec 5, 2018
712fa3a
for -> of
odersky Dec 6, 2018
2febab1
Make `instance` a reserved word
odersky Dec 10, 2018
cd33fa4
witness -> instance
odersky Dec 10, 2018
b675a41
Use new extension method syntax in instnace-defs doc
odersky Dec 10, 2018
fdb0512
ImplicitConverter -> ImplicitConversion
odersky Dec 10, 2018
63dc37b
Fix rebase breakage
odersky Dec 16, 2018
5f53757
Another blacklist update
odersky Jan 7, 2019
f708ee6
Fix rebase breakage
odersky Jan 19, 2019
07cd012
Drop redundant span computation
odersky Jan 19, 2019
6394b6f
Fix rebase breakage
odersky Jan 19, 2019
5ea1ee6
Docs reorg
odersky Jan 24, 2019
bc39a01
Add scala.Conversion
odersky Jan 25, 2019
8f6ce4b
Make `companionModule` more robust.
odersky Jan 25, 2019
e5cbc24
Implement instance as a modifier
odersky Jan 25, 2019
8d4906f
Implement anonymous context parameters
odersky Jan 25, 2019
2ebb4e6
Fix typo
odersky Jan 25, 2019
5b2a5ff
Fix synthetic parameter generation for context types
odersky Jan 26, 2019
ee09505
Fix instance translation
odersky Jan 26, 2019
32ac2f3
Fix multiple typeclass instance syntax
odersky Jan 26, 2019
139eec4
Complete test case
odersky Jan 26, 2019
cf9b3ec
Polish tests
odersky Jan 26, 2019
02737ec
Account for () insertion in class params when generating instances
odersky Jan 27, 2019
6f5df94
New syntax for alias instances
odersky Jan 27, 2019
ebb609b
Make instance defs final
odersky Jan 27, 2019
7d2a124
Blacklist a fromTasty test
odersky Jan 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2270,9 +2270,9 @@ class JSCodeGen()(implicit ctx: Context) {
if (sym == defn.BoxedUnit_UNIT) {
js.Undefined()
} else {
val instance = genLoadModule(sym.owner)
val inst = genLoadModule(sym.owner)
val method = encodeStaticMemberSym(sym)
js.Apply(instance, method, Nil)(toIRType(sym.info))
js.Apply(inst, method, Nil)(toIRType(sym.info))
}
}

Expand Down
121 changes: 93 additions & 28 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,11 @@ object desugar {
else vdef
}

def makeImplicitParameters(tpts: List[Tree], forPrimaryConstructor: Boolean = false)(implicit ctx: Context): List[ValDef] =
def makeImplicitParameters(tpts: List[Tree], contextualFlag: FlagSet = EmptyFlags, forPrimaryConstructor: Boolean = false)(implicit ctx: Context): List[ValDef] =
for (tpt <- tpts) yield {
val paramFlags: FlagSet = if (forPrimaryConstructor) PrivateLocalParamAccessor else Param
val epname = EvidenceParamName.fresh()
ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit)
ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit | contextualFlag)
}

/** 1. Expand context bounds to evidence params. E.g.,
Expand Down Expand Up @@ -197,12 +197,13 @@ object desugar {
* inline def f(x: Boolean): Any = (if (x) 1 else ""): Any
*/
private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = {
val DefDef(name, tparams, vparamss, tpt, rhs) = meth
val DefDef(_, tparams, vparamss, tpt, rhs) = meth
val methName = normalizeName(meth, tpt).asTermName
val mods = meth.mods
val epbuf = new ListBuffer[ValDef]
def desugarContextBounds(rhs: Tree): Tree = rhs match {
case ContextBounds(tbounds, cxbounds) =>
epbuf ++= makeImplicitParameters(cxbounds, isPrimaryConstructor)
epbuf ++= makeImplicitParameters(cxbounds, forPrimaryConstructor = isPrimaryConstructor)
tbounds
case LambdaTypeTree(tparams, body) =>
cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body))
Expand All @@ -213,7 +214,8 @@ object desugar {
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
}

var meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList)
var meth1 = addEvidenceParams(
cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList)

if (meth1.mods.is(Inline))
meth1.tpt match {
Expand Down Expand Up @@ -245,7 +247,7 @@ object desugar {
case (vparam :: vparams) :: vparamss1 =>
def defaultGetter: DefDef =
DefDef(
name = DefaultGetterName(meth.name, n),
name = DefaultGetterName(methName, n),
tparams = meth.tparams.map(tparam => dropContextBound(toDefParam(tparam))),
vparamss = takeUpTo(normalizedVparamss.nestedMap(toDefParam), n),
tpt = TypeTree(),
Expand Down Expand Up @@ -302,8 +304,8 @@ object desugar {

/** The expansion of a class definition. See inline comments for what is involved */
def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = {
val className = checkNotReservedName(cdef).asTypeName
val impl @ Template(_, _, self, _) = cdef.rhs
val impl @ Template(constr0, _, self, _) = cdef.rhs
val className = normalizeName(cdef, impl).asTypeName
val parents = impl.parents
val mods = cdef.mods
val companionMods = mods
Expand Down Expand Up @@ -341,6 +343,7 @@ object desugar {
val isCaseClass = mods.is(Case) && !mods.is(Module)
val isCaseObject = mods.is(Case) && mods.is(Module)
val isImplicit = mods.is(Implicit)
val isInstance = isImplicit && mods.mods.exists(_.isInstanceOf[Mod.Instance])
val isEnum = mods.isEnumClass && !mods.is(Module)
def isEnumCase = mods.isEnumCase
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
Expand Down Expand Up @@ -445,7 +448,15 @@ object desugar {
}

// new C[Ts](paramss)
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
lazy val creatorExpr = {
val vparamss = constrVparamss match {
case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters
Nil :: constrVparamss
case _ =>
constrVparamss
}
New(classTypeRef, vparamss.nestedMap(refOfDef))
}

val copiedAccessFlags = if (ctx.scala2Setting) EmptyFlags else AccessFlags

Expand Down Expand Up @@ -660,16 +671,23 @@ object desugar {
ctx.error(ImplicitCaseClass(cdef), cdef.sourcePos)
Nil
}
else if (arity != 1) {
else if (arity != 1 && !isInstance) {
ctx.error(ImplicitClassPrimaryConstructorArity(), cdef.sourcePos)
Nil
}
else
else {
val defParamss = constrVparamss match {
case Nil :: paramss =>
paramss // drop leading () that got inserted by class
// TODO: drop this once we do not silently insert empty class parameters anymore
case paramss => paramss
}
// implicit wrapper is typechecked in same scope as constructor, so
// we can reuse the constructor parameters; no derived params are needed.
DefDef(className.toTermName, constrTparams, constrVparamss, classTypeRef, creatorExpr)
.withMods(companionMods | Synthetic | Implicit)
DefDef(className.toTermName, constrTparams, defParamss, classTypeRef, creatorExpr)
.withMods(companionMods | Synthetic | Implicit | Final)
.withSpan(cdef.span) :: Nil
}

val self1 = {
val selfType = if (self.tpt.isEmpty) classTypeRef else self.tpt
Expand Down Expand Up @@ -713,9 +731,9 @@ object desugar {
* <module> final class name$ extends parents { self: name.type => body }
*/
def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = {
val moduleName = checkNotReservedName(mdef).asTermName
val impl = mdef.impl
val mods = mdef.mods
val moduleName = normalizeName(mdef, impl).asTermName
def isEnumCase = mods.isEnumCase

def flagSourcePos(flag: FlagSet) = mods.mods.find(_.flags == flag).fold(mdef.sourcePos)(_.sourcePos)
Expand Down Expand Up @@ -793,19 +811,69 @@ object desugar {
Thicket(aliasType :: companions.toList)
}

/** The name of `mdef`, after checking that it does not redefine a Scala core class.
* If it does redefine, issue an error and return a mangled name instead of the original one.
/** The normalized name of `mdef`. This means
* 1. Check that the name does not redefine a Scala core class.
* If it does redefine, issue an error and return a mangled name instead of the original one.
* 2. If the name is missing (this can be the case for instance definitions), invent one instead.
*/
def checkNotReservedName(mdef: MemberDef)(implicit ctx: Context): Name = {
val name = mdef.name
def normalizeName(mdef: MemberDef, impl: Tree)(implicit ctx: Context): Name = {
var name = mdef.name
if (name.isEmpty) name = name.likeSpaced(s"${inventName(impl)}_instance".toTermName)
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
def kind = if (name.isTypeName) "class" else "object"
ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos)
name.errorName
name = name.errorName
}
else name
name
}

/** Invent a name for an anonymous instance with template `impl`.
*/
private def inventName(impl: Tree)(implicit ctx: Context): String = impl match {
case impl: Template =>
if (impl.parents.isEmpty)
impl.body.find {
case dd: DefDef if dd.mods.is(Extension) => true
case _ => false
} match {
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
s"${name}_of_${inventTypeName(vparam.tpt)}"
case _ =>
ctx.error(i"anonymous instance must have `for` part or must define at least one extension method", impl.sourcePos)
nme.ERROR.toString
}
else
impl.parents.map(inventTypeName(_)).mkString("_")
case impl: Tree =>
inventTypeName(impl)
}

private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] {
private def extractArgs(args: List[Tree])(implicit ctx: Context): String =
args.map(argNameExtractor.apply("", _)).mkString("_")
override def apply(x: String, tree: Tree)(implicit ctx: Context): String =
if (x.isEmpty)
tree match {
case Select(pre, nme.CONSTRUCTOR) => foldOver(x, pre)
case tree: RefTree if tree.name.isTypeName => tree.name.toString
case tree: TypeDef => tree.name.toString
case tree: AppliedTypeTree if followArgs && tree.args.nonEmpty =>
s"${apply(x, tree.tpt)}_${extractArgs(tree.args)}"
case tree: LambdaTypeTree =>
apply(x, tree.body)
case tree: Tuple =>
if (followArgs) extractArgs(tree.trees) else "Tuple"
case tree: Function if tree.args.nonEmpty =>
if (followArgs) s"${extractArgs(tree.args)}_to_${apply("", tree.body)}" else "Function"
case _ => foldOver(x, tree)
}
else x
}
private val typeNameExtractor = new NameExtractor(followArgs = true)
private val argNameExtractor = new NameExtractor(followArgs = false)

private def inventTypeName(tree: Tree)(implicit ctx: Context): String = typeNameExtractor("", tree)

/** val p1, ..., pN: T = E
* ==>
* makePatDef[[val p1: T1 = E]]; ...; makePatDef[[val pN: TN = E]]
Expand Down Expand Up @@ -960,14 +1028,11 @@ object desugar {
* def $anonfun(params) = body
* Closure($anonfun)
*/
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = null, isImplicit: Boolean)(implicit ctx: Context): Block = {
val span = params.headOption.fold(body.span)(_.span.union(body.span))
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = null, isContextual: Boolean)(implicit ctx: Context): Block =
Block(
DefDef(nme.ANON_FUN, Nil, params :: Nil, if (tpt == null) TypeTree() else tpt, body)
.withSpan(span)
.withMods(synthetic | Artifact),
Closure(Nil, Ident(nme.ANON_FUN), if (isImplicit) ImplicitEmptyTree else EmptyTree)).withSpan(span)
}
Closure(Nil, Ident(nme.ANON_FUN), if (isContextual) ContextualEmptyTree else EmptyTree))

/** If `nparams` == 1, expand partial function
*
Expand Down Expand Up @@ -1020,9 +1085,9 @@ object desugar {
Function(param :: Nil, Block(vdefs, body))
}

def makeImplicitFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = {
val params = makeImplicitParameters(formals.map(TypeTree))
new FunctionWithMods(params, body, Modifiers(Implicit))
def makeContextualFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = {
val params = makeImplicitParameters(formals.map(TypeTree), Contextual)
new FunctionWithMods(params, body, Modifiers(Implicit | Contextual))
}

/** Add annotation to tree:
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,15 +330,15 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
functionWithUnknownParamType(tree).isDefined

/** Is `tree` an implicit function or closure, possibly nested in a block? */
def isImplicitClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match {
case tree: FunctionWithMods => tree.mods.is(Implicit)
case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Implicit)
def isContextualClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match {
case tree: FunctionWithMods => tree.mods.is(Contextual)
case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Contextual)
case Closure(_, meth, _) => true
case Block(Nil, expr) => isImplicitClosure(expr)
case Block(Nil, expr) => isContextualClosure(expr)
case Block(DefDef(nme.ANON_FUN, _, params :: _, _, _) :: Nil, cl: Closure) =>
params match {
case param :: _ => param.mods.is(Implicit)
case Nil => cl.tpt.eq(untpd.ImplicitEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt)
case param :: _ => param.mods.is(Contextual)
case Nil => cl.tpt.eq(untpd.ContextualEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt)
}
case _ => false
}
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,8 @@ object Trees {
case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
extends GenericApply[T] {
type ThisTree[-T >: Untyped] = Apply[T]

def isContextual = getAttachment(untpd.WithApply).nonEmpty
}

/** fun[args] */
Expand Down Expand Up @@ -951,7 +953,7 @@ object Trees {

@sharable val EmptyTree: Thicket = genericEmptyTree
@sharable val EmptyValDef: ValDef = genericEmptyValDef
@sharable val ImplicitEmptyTree: Thicket = new EmptyTree // an empty tree marking an implicit closure
@sharable val ContextualEmptyTree: Thicket = new EmptyTree // an empty tree marking a contextual closure

// ----- Auxiliary creation methods ------------------

Expand Down
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
val previousParamRefs = if (isParamDependent) new mutable.ListBuffer[TermRef]() else null

def valueParam(name: TermName, origInfo: Type): TermSymbol = {
val maybeImplicit = if (tp.isImplicitMethod) Implicit else EmptyFlags
val maybeImplicit =
if (tp.isContextual) Implicit | Contextual
else if (tp.isImplicitMethod) Implicit
else EmptyFlags
val maybeErased = if (tp.isErasedMethod) Erased else EmptyFlags

def makeSym(info: Type) = ctx.newSymbol(sym, name, TermParam | maybeImplicit | maybeErased, info, coord = sym.coord)
Expand Down Expand Up @@ -1039,9 +1042,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}
}

def applyOverloaded(receiver: Tree, method: TermName, args: List[Tree], targs: List[Type], expectedType: Type)(implicit ctx: Context): Tree = {
def applyOverloaded(receiver: Tree, method: TermName, args: List[Tree], targs: List[Type],
expectedType: Type, isContextual: Boolean = false)(implicit ctx: Context): Tree = {
val typer = ctx.typer
val proto = new FunProtoTyped(args, expectedType)(typer)
val proto = new FunProtoTyped(args, expectedType)(typer, isContextual)
val denot = receiver.tpe.member(method)
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
val selected =
Expand Down
12 changes: 9 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
override def isType: Boolean = body.isType
}

/** A function type with `implicit` or `erased` modifiers */
/** A function type with `implicit`, `erased`, or `contextual` modifiers */
class FunctionWithMods(args: List[Tree], body: Tree, val mods: Modifiers)(implicit @constructorOnly src: SourceFile)
extends Function(args, body)

Expand Down Expand Up @@ -149,6 +149,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class Inline()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Inline)

case class Enum()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Enum)

case class Instance()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Implicit)
}

/** Modifiers and annotations for definitions
Expand Down Expand Up @@ -213,6 +215,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def hasFlags: Boolean = flags != EmptyFlags
def hasAnnotations: Boolean = annotations.nonEmpty
def hasPrivateWithin: Boolean = privateWithin != tpnme.EMPTY
def hasMod(cls: Class[_]) = mods.exists(_.getClass == cls)

private def isEnum = is(Enum, butNot = JavaDefined)

Expand Down Expand Up @@ -269,6 +272,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
*/
val OriginalSymbol: Property.Key[Symbol] = new Property.Key

/** Property key for contextual Apply trees of the form `fn with arg` */
val WithApply: Property.StickyKey[Unit] = new Property.StickyKey

// ------ Creation methods for untyped only -----------------

def Ident(name: Name)(implicit src: SourceFile): Ident = new Ident(name)
Expand Down Expand Up @@ -398,9 +404,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def makeParameter(pname: TermName, tpe: Tree, mods: Modifiers = EmptyModifiers)(implicit ctx: Context): ValDef =
ValDef(pname, tpe, EmptyTree).withMods(mods | Param)

def makeSyntheticParameter(n: Int = 1, tpt: Tree = null)(implicit ctx: Context): ValDef =
def makeSyntheticParameter(n: Int = 1, tpt: Tree = null, flags: FlagSet = EmptyFlags)(implicit ctx: Context): ValDef =
ValDef(nme.syntheticParamName(n), if (tpt == null) TypeTree() else tpt, EmptyTree)
.withFlags(SyntheticTermParam)
.withFlags(flags | SyntheticTermParam)

def lambdaAbstract(tparams: List[TypeDef], tpt: Tree)(implicit ctx: Context): Tree =
if (tparams.isEmpty) tpt else LambdaTypeTree(tparams, tpt)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object Printers {
val checks: Printer = noPrinter
val config: Printer = noPrinter
val cyclicErrors: Printer = noPrinter
val debug = noPrinter
val debug = noPrinter // no type annotion here to force inlining
val derive: Printer = noPrinter
val dottydoc: Printer = noPrinter
val exhaustivity: Printer = noPrinter
Expand Down
Loading