Skip to content

Commit 23164bc

Browse files
bracevacnatsukagami
authored andcommitted
Bullet-proof function-arrow rendering
1 parent 85af83d commit 23164bc

File tree

3 files changed

+57
-33
lines changed

3 files changed

+57
-33
lines changed

local/project/dummy/arrows.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dummy
22

33
import language.experimental.captureChecking
4-
import language.experimental.pureFunctions
54
import caps.*
65

76
trait Nested:
@@ -18,6 +17,15 @@ trait Arrows:
1817
val impurev: Int => Int
1918
val impurev2: Int ->{a,b,c} Int
2019
val impurev3: Int ->{a,b,c} Int => Int
20+
val impureCap: Int ->{cap} Int
21+
val impureCap2: Int ->{cap, a, b, c} Int
22+
val contextPureV: Int ?-> Int
23+
val contextPureV2: Int ?->{} Int
24+
val contextImpureV: Int ?=> Int
25+
val contextImpureV2: Int ?->{a,b,c} Int
26+
val contextImpureV3: Int ?->{a,b,c} Int ?=> Int
27+
val contextImpureCap: Int ?->{cap} Int
28+
val contextImpureCap2: Int ?->{cap, a, b, c} Int
2129

2230
def pure(f: Int -> Int): Int
2331
def pure2(f: Int ->{} Int): Int
@@ -46,7 +54,7 @@ trait Arrows:
4654
def pathDependent2(n: Nested^)(g: AnyRef^{n.next.c} => Any): Any
4755
def pathDependent3(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): Any
4856
def pathDependent4(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c}
49-
def pathDependent5(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c*, n.c}
57+
def pathDependent5(n: Nested^)(g: AnyRef^{n.c} => AnyRef^{n.next.c} ->{n.c} Any): AnyRef^{n.next.next.c*, n.c, cap}
5058

5159
def contextPure(f: AnyRef^{a} ?-> Int): Int
5260
def contextImpure(f: AnyRef^{a} ?=> Int): Int

scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ object CaptureDefs:
4949
def Function1(using qctx: Quotes) =
5050
qctx.reflect.Symbol.requiredClass("scala.Function1")
5151

52+
def ContextFunction1(using qctx: Quotes) =
53+
qctx.reflect.Symbol.requiredClass("scala.ContextFunction1")
54+
5255
val ccImportSelector = "captureChecking"
5356
end CaptureDefs
5457

@@ -65,14 +68,26 @@ extension (using qctx: Quotes)(ann: qctx.reflect.Symbol)
6568
ann == CaptureDefs.ReachCapabilityAnnot
6669
end extension
6770

68-
extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr)
71+
extension (using qctx: Quotes)(tpe: qctx.reflect.TypeRepr) // FIXME clean up and have versions on Symbol for those
6972
def isCaptureRoot: Boolean = tpe.termSymbol == CaptureDefs.captureRoot
7073

71-
def isImpureFunction1: Boolean = tpe.derivesFrom(CaptureDefs.ImpureFunction1)
74+
// NOTE: There's something horribly broken with Symbols, and we can't rely on tests like .isContextFunctionType either,
75+
// so we do these lame string comparisons instead.
76+
def isImpureFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureFunction1"
77+
78+
def isImpureContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ImpureContextFunction1"
79+
80+
def isFunction1: Boolean = tpe.typeSymbol.fullName == "scala.Function1"
81+
82+
def isContextFunction1: Boolean = tpe.typeSymbol.fullName == "scala.ContextFunction1"
83+
84+
def isAnyImpureFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureFunction")
85+
86+
def isAnyImpureContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ImpureContextFunction")
7287

73-
def isImpureContextFunction1: Boolean = tpe.derivesFrom(CaptureDefs.ImpureContextFunction1)
88+
def isAnyFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.Function")
7489

75-
def isFunction1: Boolean = tpe.derivesFrom(CaptureDefs.Function1)
90+
def isAnyContextFunction: Boolean = tpe.typeSymbol.fullName.startsWith("scala.ContextFunction")
7691
end extension
7792

7893
/** Matches `import scala.language.experimental.captureChecking` */

scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ trait TypesSupport:
124124
++ keyword(" & ").l
125125
++ inParens(inner(right, skipThisTypePrefix), shouldWrapInParens(right, tp, false))
126126
case ByNameType(CapturingType(tpe, refs)) =>
127-
renderFunctionArrow(using q)(refs, FunKind(true, false), skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix))
127+
renderByNameArrow(using q)(Some(refs), skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix))
128128
case ByNameType(tpe) =>
129-
(if ccEnabled then keyword("-> ") else keyword("=> ")):: inner(tpe, skipThisTypePrefix)
129+
renderByNameArrow(using q)(None, skipThisTypePrefix) ++ (plain(" ") :: inner(tpe, skipThisTypePrefix))
130130
case ConstantType(constant) =>
131131
plain(constant.show).l
132132
case ThisType(tpe) =>
@@ -139,7 +139,7 @@ trait TypesSupport:
139139
inner(tpe, skipThisTypePrefix) :+ plain("*")
140140
case CapturingType(base, refs) => base match
141141
case t @ AppliedType(base, args) if t.isFunctionType =>
142-
functionType(t, base, args, skipThisTypePrefix)(using inCC = Some(refs))
142+
functionType(base, args, skipThisTypePrefix)(using inCC = Some(refs))
143143
case _ => inner(base, skipThisTypePrefix) ++ renderCapturing(refs, skipThisTypePrefix)
144144
case AnnotatedType(tpe, _) =>
145145
inner(tpe, skipThisTypePrefix)
@@ -258,7 +258,7 @@ trait TypesSupport:
258258
++ inParens(inner(rhs, skipThisTypePrefix), shouldWrapInParens(rhs, t, false))
259259

260260
case t @ AppliedType(tpe, args) if t.isFunctionType =>
261-
functionType(t, tpe, args, skipThisTypePrefix)
261+
functionType(tpe, args, skipThisTypePrefix)
262262

263263
case t @ AppliedType(tpe, typeList) =>
264264
inner(tpe, skipThisTypePrefix) ++ plain("[").l ++ commas(typeList.map { t => t match
@@ -346,19 +346,14 @@ trait TypesSupport:
346346
s"${tpe.show(using Printer.TypeReprStructure)}"
347347
throw MatchError(msg)
348348

349-
private def functionType(using q: Quotes)(t: reflect.TypeRepr, tpe: reflect.TypeRepr, args: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using
349+
private def functionType(using q: Quotes)(funTy: reflect.TypeRepr, args: List[reflect.TypeRepr], skipThisTypePrefix: Boolean)(using
350350
elideThis: reflect.ClassDef,
351351
indent: Int,
352352
originalOwner: reflect.Symbol,
353353
inCC: Option[List[reflect.TypeRepr]],
354354
): SSignature =
355355
import reflect._
356-
val refs = if !inCC.isDefined && t.isContextFunctionType then
357-
// This'll ensure that an impure context function type is rendered correctly
358-
Some(List(CaptureDefs.captureRoot.termRef))
359-
else
360-
inCC
361-
val arrow = plain(" ") :: (renderFunctionArrow(using q)(refs, FunKind(isPure = t.isFunction1, isImplicit = t.isContextFunctionType), skipThisTypePrefix) ++ plain(" ").l)
356+
val arrow = plain(" ") :: (renderFunctionArrow(using q)(funTy, inCC, skipThisTypePrefix) ++ plain(" ").l)
362357
given Option[List[TypeRepr]] = None // FIXME: this is ugly
363358
args match
364359
case Nil => Nil
@@ -507,27 +502,33 @@ trait TypesSupport:
507502
import reflect._
508503
Keyword("^") :: renderCaptureSet(refs, skipThisTypePrefix)
509504

510-
private def renderFunctionArrow(using q: Quotes)(refs: List[reflect.TypeRepr], fun: FunKind, skipThisTypePrefix: Boolean)(
505+
private def renderFunctionArrow(using q: Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(
511506
using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol
512507
): SSignature =
513508
import reflect._
514-
val prefix = if fun.isImplicit then "?" else ""
509+
val isContextFun = funTy.isAnyContextFunction || funTy.isAnyImpureContextFunction
510+
val prefix = if isContextFun then "?" else ""
515511
if !ccEnabled then
516512
List(Keyword(prefix + "=>"))
517513
else
518-
refs match
519-
case Nil => if fun.isPure then List(Keyword(prefix + "->")) else List(Keyword(prefix + "=>"))
520-
case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>"))
521-
case refs => Keyword(prefix + "->") :: renderCaptureSet(using q)(refs, skipThisTypePrefix)
522-
523-
private def renderFunctionArrow(using q: Quotes)(refs: Option[List[reflect.TypeRepr]], fun: FunKind, skipThisTypePrefix: Boolean)(
514+
val isPureFun = funTy.isAnyFunction || funTy.isAnyContextFunction
515+
val isImpureFun = funTy.isAnyImpureFunction || funTy.isAnyImpureContextFunction
516+
captures match
517+
case None => // means an explicit retains* annotation is missing
518+
if isPureFun then
519+
List(Keyword(prefix + "->"))
520+
else if isImpureFun then
521+
List(Keyword(prefix + "=>"))
522+
else
523+
report.error(s"Cannot render function arrow: expected a (Context)Function* or Impure(Context)Function*, but got: ${funTy.show}")
524+
Nil
525+
case Some(refs) => // there is some capture set
526+
refs match
527+
case Nil => List(Keyword(prefix + "->"))
528+
case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>"))
529+
case refs => Keyword(prefix + "->") :: renderCaptureSet(using q)(refs, skipThisTypePrefix)
530+
531+
private def renderByNameArrow(using q: Quotes)(captures: Option[List[reflect.TypeRepr]], skipThisTypePrefix: Boolean)(
524532
using elideThis: reflect.ClassDef, originalOwner: reflect.Symbol
525533
): SSignature =
526-
import reflect._
527-
val prefix = if fun.isImplicit then "?" else ""
528-
if !ccEnabled then
529-
List(Keyword(prefix + "=>"))
530-
else
531-
refs match
532-
case None => if fun.isPure then List(Keyword(prefix + "->")) else List(Keyword(prefix + "=>"))
533-
case Some(refs) => renderFunctionArrow(using q)(refs, fun, skipThisTypePrefix)
534+
renderFunctionArrow(using q)(CaptureDefs.Function1.typeRef, captures, skipThisTypePrefix)

0 commit comments

Comments
 (0)