Skip to content

Commit 6f5df94

Browse files
committed
New syntax for alias instances
Replaces "instance as a modifier scheme". Advantage: Simpler, fewer possible feture interactions. Disadvantage: Less straightforward to convert existsing implicits to new scheme.
1 parent 02737ec commit 6f5df94

File tree

10 files changed

+163
-102
lines changed

10 files changed

+163
-102
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ object desugar {
197197
* inline def f(x: Boolean): Any = (if (x) 1 else ""): Any
198198
*/
199199
private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = {
200-
val DefDef(name, tparams, vparamss, tpt, rhs) = meth
200+
val DefDef(_, tparams, vparamss, tpt, rhs) = meth
201+
val methName = normalizeName(meth, tpt).asTermName
201202
val mods = meth.mods
202203
val epbuf = new ListBuffer[ValDef]
203204
def desugarContextBounds(rhs: Tree): Tree = rhs match {
@@ -213,7 +214,8 @@ object desugar {
213214
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
214215
}
215216

216-
var meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList)
217+
var meth1 = addEvidenceParams(
218+
cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList)
217219

218220
if (meth1.mods.is(Inline))
219221
meth1.tpt match {
@@ -245,7 +247,7 @@ object desugar {
245247
case (vparam :: vparams) :: vparamss1 =>
246248
def defaultGetter: DefDef =
247249
DefDef(
248-
name = DefaultGetterName(meth.name, n),
250+
name = DefaultGetterName(methName, n),
249251
tparams = meth.tparams.map(tparam => dropContextBound(toDefParam(tparam))),
250252
vparamss = takeUpTo(normalizedVparamss.nestedMap(toDefParam), n),
251253
tpt = TypeTree(),
@@ -303,7 +305,7 @@ object desugar {
303305
/** The expansion of a class definition. See inline comments for what is involved */
304306
def classDef(cdef: TypeDef)(implicit ctx: Context): Tree = {
305307
val impl @ Template(constr0, _, self, _) = cdef.rhs
306-
val className = normalizeClassName(cdef, impl)
308+
val className = normalizeName(cdef, impl).asTypeName
307309
val parents = impl.parents
308310
val mods = cdef.mods
309311
val companionMods = mods
@@ -731,7 +733,7 @@ object desugar {
731733
def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = {
732734
val impl = mdef.impl
733735
val mods = mdef.mods
734-
val moduleName = normalizeClassName(mdef, impl).toTermName
736+
val moduleName = normalizeName(mdef, impl).asTermName
735737
def isEnumCase = mods.isEnumCase
736738

737739
def flagSourcePos(flag: FlagSet) = mods.mods.find(_.flags == flag).fold(mdef.sourcePos)(_.sourcePos)
@@ -814,10 +816,10 @@ object desugar {
814816
* If it does redefine, issue an error and return a mangled name instead of the original one.
815817
* 2. If the name is missing (this can be the case for instance definitions), invent one instead.
816818
*/
817-
def normalizeClassName(mdef: MemberDef, impl: Template)(implicit ctx: Context): TypeName = {
818-
var name = mdef.name.toTypeName
819-
if (name.isEmpty) name = s"${inventName(impl)}_instance".toTypeName
820-
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name)) {
819+
def normalizeName(mdef: MemberDef, impl: Tree)(implicit ctx: Context): Name = {
820+
var name = mdef.name
821+
if (name.isEmpty) name = name.likeSpaced(s"${inventName(impl)}_instance".toTermName)
822+
if (ctx.owner == defn.ScalaPackageClass && defn.reservedScalaClassNames.contains(name.toTypeName)) {
821823
def kind = if (name.isTypeName) "class" else "object"
822824
ctx.error(em"illegal redefinition of standard $kind $name", mdef.sourcePos)
823825
name = name.errorName
@@ -827,20 +829,24 @@ object desugar {
827829

828830
/** Invent a name for an anonymous instance with template `impl`.
829831
*/
830-
private def inventName(impl: Template)(implicit ctx: Context): String =
831-
if (impl.parents.isEmpty)
832-
impl.body.find {
833-
case dd: DefDef if dd.mods.is(Extension) => true
834-
case _ => false
835-
} match {
836-
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
837-
s"${name}_of_${inventTypeName(vparam.tpt)}"
838-
case _ =>
839-
ctx.error(i"anonymous instance must have `for` part or must define at least one extension method", impl.sourcePos)
840-
nme.ERROR.toString
841-
}
842-
else
843-
impl.parents.map(inventTypeName(_)).mkString("_")
832+
private def inventName(impl: Tree)(implicit ctx: Context): String = impl match {
833+
case impl: Template =>
834+
if (impl.parents.isEmpty)
835+
impl.body.find {
836+
case dd: DefDef if dd.mods.is(Extension) => true
837+
case _ => false
838+
} match {
839+
case Some(DefDef(name, _, (vparam :: _) :: _, _, _)) =>
840+
s"${name}_of_${inventTypeName(vparam.tpt)}"
841+
case _ =>
842+
ctx.error(i"anonymous instance must have `for` part or must define at least one extension method", impl.sourcePos)
843+
nme.ERROR.toString
844+
}
845+
else
846+
impl.parents.map(inventTypeName(_)).mkString("_")
847+
case impl: Tree =>
848+
inventTypeName(impl)
849+
}
844850

845851
private class NameExtractor(followArgs: Boolean) extends UntypedTreeAccumulator[String] {
846852
private def extractArgs(args: List[Tree])(implicit ctx: Context): String =

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2305,9 +2305,8 @@ object Parsers {
23052305
}
23062306
}
23072307

2308-
/** DefDef ::= MethodDef | ConstructorDef
2309-
* MethodDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
2310-
* ConstructorDef ::= this ParamClause ParamClauses `=' ConstrExpr
2308+
/** DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr
2309+
* | this ParamClause ParamClauses `=' ConstrExpr
23112310
* DefDcl ::= DefSig `:' Type
23122311
* DefSig ::= ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] ParamClauses
23132312
*/
@@ -2321,8 +2320,7 @@ object Parsers {
23212320
true
23222321
}
23232322
}
2324-
val isInstance = mods.hasMod(Predef.classOf[Mod.Instance])
2325-
if (!isInstance && in.token == THIS) {
2323+
if (in.token == THIS) {
23262324
in.nextToken()
23272325
val vparamss = paramClauses()
23282326
if (vparamss.isEmpty || vparamss.head.take(1).exists(_.mods.is(Implicit)))
@@ -2346,7 +2344,7 @@ object Parsers {
23462344
val mods1 = addFlag(mods, flags)
23472345
val name = ident()
23482346
val tparams = typeParamClauseOpt(ParamOwner.Def)
2349-
val vparamss = paramClauses(ofInstance = isInstance) match {
2347+
val vparamss = paramClauses() match {
23502348
case rparams :: rparamss if leadingParamss.nonEmpty && !isLeftAssoc(name) =>
23512349
rparams :: leadingParamss ::: rparamss
23522350
case rparamss =>
@@ -2555,38 +2553,36 @@ object Parsers {
25552553
Template(constr, parents, Nil, EmptyValDef, Nil)
25562554
}
25572555

2558-
/** InstanceDef ::= [id] InstanceParams [‘of’ ConstrApp {‘,’ ConstrApp}] [TemplateBody]
2559-
* | ‘val’ PatDef
2560-
* | ‘def’ MethodDef
2556+
/** InstanceDef ::= [id] InstanceParams InstanceBody
25612557
* InstanceParams ::= [DefTypeParamClause] {InstParamClause}
2558+
* InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody]
2559+
* | ‘of’ Type ‘=’ Expr
25622560
*/
2563-
def instanceDef(start: Offset, mods: Modifiers, instanceMod: Mod) = {
2564-
val mods1 = addMod(mods, instanceMod)
2565-
if (in.token == VAL) {
2566-
in.nextToken()
2567-
patDefOrDcl(start, mods1)
2568-
}
2569-
else if (in.token == DEF) {
2570-
in.nextToken()
2571-
defDefOrDcl(start, mods1)
2572-
}
2573-
else atSpan(start, nameStart) {
2574-
val name = if (isIdent && !isIdent(nme.of)) ident() else EmptyTermName
2575-
val tparams = typeParamClauseOpt(ParamOwner.Class)
2576-
val vparamss = paramClauses(ofClass = true, ofInstance = true)
2577-
val parents =
2578-
if (isIdent(nme.of)) {
2579-
in.nextToken()
2580-
tokenSeparated(COMMA, constrApp)
2581-
}
2582-
else Nil
2583-
newLineOptWhenFollowedBy(LBRACE)
2584-
val templ = templateBodyOpt(makeConstructor(tparams, vparamss), parents, Nil, isEnum = false)
2585-
val instDef =
2561+
def instanceDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) {
2562+
val name = if (isIdent && !isIdent(nme.of)) ident() else EmptyTermName
2563+
val tparams = typeParamClauseOpt(ParamOwner.Def)
2564+
val vparamss = paramClauses(ofInstance = true)
2565+
val parents =
2566+
if (isIdent(nme.of)) {
2567+
in.nextToken()
2568+
tokenSeparated(COMMA, constrApp)
2569+
}
2570+
else Nil
2571+
val instDef =
2572+
if (in.token == EQUALS && parents.length == 1 && parents.head.isType) {
2573+
in.nextToken()
2574+
DefDef(name, tparams, vparamss, parents.head, expr())
2575+
}
2576+
else {
2577+
newLineOptWhenFollowedBy(LBRACE)
2578+
val tparams1 = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal))
2579+
val vparamss1 = vparamss.map(_.map(vparam =>
2580+
vparam.withMods(vparam.mods &~ Param | ParamAccessor | PrivateLocal)))
2581+
val templ = templateBodyOpt(makeConstructor(tparams1, vparamss1), parents, Nil, isEnum = false)
25862582
if (tparams.isEmpty && vparamss.isEmpty) ModuleDef(name, templ)
25872583
else TypeDef(name.toTypeName, templ)
2588-
finalizeDef(instDef, mods1, start)
2589-
}
2584+
}
2585+
finalizeDef(instDef, addMod(mods, instanceMod), start)
25902586
}
25912587

25922588
/* -------- TEMPLATES ------------------------------------------- */

docs/docs/internals/syntax.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,9 @@ Def ::= ‘val’ PatDef
358358
PatDef ::= Pattern2 {‘,’ Pattern2} [‘:’ Type] ‘=’ Expr PatDef(_, pats, tpe?, expr)
359359
VarDef ::= PatDef
360360
| ids ‘:’ Type ‘=’ ‘_’
361-
DefDef ::= MethodDef | ConstructorDef
362-
MethodDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr DefDef(_, name, tparams, vparamss, tpe, expr)
361+
DefDef ::= DefSig [(‘:’ | ‘<:’) Type] ‘=’ Expr DefDef(_, name, tparams, vparamss, tpe, expr)
363362
| DefSig [nl] ‘{’ Block ‘}’ DefDef(_, name, tparams, vparamss, tpe, Block)
364-
ConstructorDef ::= ‘this’ DefParamClause DefParamClauses DefDef(_, <init>, Nil, vparamss, EmptyTree, expr | Block)
363+
| ‘this’ DefParamClause DefParamClauses DefDef(_, <init>, Nil, vparamss, EmptyTree, expr | Block)
365364
(‘=’ ConstrExpr | [nl] ConstrBlock)
366365
367366
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
@@ -373,11 +372,10 @@ ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
373372
ConstrMods ::= {Annotation} [AccessModifier]
374373
ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor
375374
EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template)
376-
InstanceDef ::= [id] InstanceParams [‘of’ ConstrApp {‘,’ ConstrApp}]
377-
[TemplateBody]
378-
| ‘val’ PatDef
379-
| ‘def’ MethodDef
375+
InstanceDef ::= [id] InstanceParams InstanceBody
380376
InstanceParams ::= [DefTypeParamClause] {InstParamClause}
377+
InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody]
378+
| ‘of’ Type ‘=’ Expr
381379
Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats)
382380
InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}]
383381
ConstrApps ::= ConstrApp {‘with’ ConstrApp}

docs/docs/reference/instances/context-params.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ f("abc") with ctx
6868
Context parameters may be given either as a normal parameter list `(...)`
6969
or as a sequence of types. To distinguish the two, a leading `(` always indicates a parameter list.
7070

71+
## Summoning an Instance
72+
73+
A method `summon` in `Predef` creates an implicit instance value for a given type, analogously to what `implicitly[T]` did. The only difference between the two is that
74+
`summon` takes a context parameter, where `implicitly` took an old-style implicit parameter:
75+
```scala
76+
def summon[T] with (x: T) = x
77+
```
78+
7179
## Syntax
7280

7381
Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html).

docs/docs/reference/instances/instance-defs.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,35 @@ instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] {
161161
ctx => x
162162
}
163163
```
164+
## Alias Instances
165+
166+
An alias instance creates an instance that is equal to some expression. E.g.,
167+
```
168+
instance ctx of ExecutionContext = currentThreadPool().context
169+
```
170+
Here, we create an instance `ctx` of type `ExecutionContext` that resolves to the
171+
right hand side `currentThreadPool().context`. Each time an instance of `ExecutionContext`
172+
is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition:
173+
```
174+
final implicit def ctx: ExecutionContext = currentThreadPool().context
175+
```
176+
Alias instances may be anonymous, e.g.
177+
```
178+
instance of Position = enclosingTree.position
179+
```
180+
An alias instance can have type and context parameters just like any other instance definition, but it can only implement a single type.
164181

165182
## Syntax
166183

167184
Here is the new syntax of instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html).
168185
```
169186
TmplDef ::= ...
170187
| ‘instance’ InstanceDef
171-
InstanceDef ::= [id] InstanceParams [‘of’ ConstrApp {‘,’ ConstrApp}] [TemplateBody]
188+
InstanceDef ::= [id] InstanceParams InstanceBody
172189
InstanceParams ::= [DefTypeParamClause] {InstParamClause}
173190
InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes)
191+
InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody]
192+
| ‘of’ Type ‘=’ Expr
174193
ContextTypes ::= RefinedType {‘,’ RefinedType}
175194
```
176-
The identifier `id` can be omitted only if either the `of` part or the template body is present. If the `of` part is missing, the template body must define at least one extension method.
195+
The identifier `id` can be omitted only if either the `of` part or the template body is present. If the `of` part is missing, the template body must define at least one extension method.

docs/docs/reference/instances/replacing-implicits.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,26 @@ These idioms can by-and-large be mapped to existing implicits. The only exceptio
99

1010
The contents of this page are more tentative than the ones of the previous pages. The concepts described in the previous pages are useful independently whether the changes on this page are adopted.
1111

12-
The current Dotty implementation implements the new concepts described on this page (implicit as a modifier and the summon method), but it does not remove any of the old-style implicit constructs. It cannot do this since support
12+
The current Dotty implementation implements the new concepts described on this page (alias instances and the summon method), but it does not remove any of the old-style implicit constructs. It cannot do this since support
1313
for old-style implicits is an essential part of the common language subset of Scala 2 and Scala 3.0. Any deprecation and subsequent removal of these constructs would have to come later, in a version following 3.0. The `implicit` modifier can be removed from the language at the end of this development, if it happens.
1414

15-
## `instance` As A Modifier.
15+
## Alias Instances
1616

17-
`instance` can be used as a modifier for `val` and `def` definitions. Examples:
18-
```scala
19-
instance val symDeco: SymDeco
20-
instance val symDeco: SymDeco = compilerSymOps
21-
instance val ctx = localCtx
22-
instance def f[T]: C[T] = new C[T]
23-
instance def g with (ctx: Context): D = new D(ctx)
17+
An alias instance creates an instance that is equal to some expression.
2418
```
25-
The `instance` modifier must be followed directly by `val` or `def`; no other intervening modifiers are permitted.
26-
When used as a modifier, `instance` generally has the same meaning as the current `implicit` modifier, with the following exceptions:
27-
28-
1. `instance def` definitions can only have context parameters in `with` clauses. Old style `implicit` parameters are not supported.
29-
2. `instance` cannot be used to define an implicit conversion or an implicit class.
30-
19+
instance ctx of ExecutionContext = currentThreadPool().context
20+
```
21+
Here, we create an instance `ctx` of type `ExecutionContext` that resolves to the
22+
right hand side `currentThreadPool().context`. Each time an instance of `ExecutionContext`
23+
is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition:
24+
```
25+
final implicit def ctx: ExecutionContext = currentThreadPool().context
26+
```
27+
Alias instances may be anonymous, e.g.
28+
```
29+
instance of Position = enclosingTree.position
30+
```
31+
An alias instance can have type and context parameters just like any other instance definition, but it can only implement a single type.
3132

3233
## Replaced: Implicit Conversions
3334

@@ -59,9 +60,8 @@ def summon[T] with (x: T) = x
5960

6061
The syntax changes for this page are summarized as follows:
6162
```
62-
InstanceDef ::= ...
63-
| ‘val’ PatDef
64-
| ‘def’ MethodDef
63+
InstanceBody ::= ...
64+
| ‘of’ Type ‘=’ Expr
6565
```
6666
In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it.
6767

docs/sidebar.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ sidebar:
7373
- title: Context Parameters
7474
url: docs/reference/instances/context-params.html
7575
- title: Implicit Function Types
76-
url: docs/reference/new-types/implicit-function-types.html
76+
url: docs/reference/instances/implicit-function-types.html
77+
- title: Implicit Conversions
78+
url: docs/reference/instances/implicit-conversions.html
7779
- title: Replacing Implicits
7880
url: docs/reference/instances/replacing-implicits.html
7981
- title: Discussion

tests/new/test.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ trait T {
22
object O
33
}
44

5+
object Test0 {
6+
trait A[T]
7+
instance a[T] of A[T]
8+
9+
class B[T]
10+
instance b[T] of B[T]
11+
}
12+
513
class C extends T
614

715
object Test {
8-
916
val c = new C
1017
c.O
11-
1218
}

tests/pos/assumeIn.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ object Test {
33

44
class Context {
55
inline def assumeIn[T](op: => Context |=> T) = {
6-
instance def ctx: Context = this
6+
instance of Context = this
77
op
88
}
99
}

0 commit comments

Comments
 (0)