Skip to content

Commit a387913

Browse files
committed
Bullet-proof function-arrow rendering
1 parent 054b78d commit a387913

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
@@ -115,9 +115,9 @@ trait TypesSupport:
115115
++ keyword(" & ").l
116116
++ inParens(inner(right), shouldWrapInParens(right, tp, false))
117117
case ByNameType(CapturingType(tpe, refs)) =>
118-
renderFunctionArrow(using qctx)(refs, true, false) ++ (plain(" ") :: inner(tpe))
118+
renderByNameArrow(using qctx)(Some(refs)) ++ (plain(" ") :: inner(tpe))
119119
case ByNameType(tpe) =>
120-
(if ccEnabled then keyword("-> ") else keyword("=> ")):: inner(tpe)
120+
renderByNameArrow(using qctx)(None) ++ (plain(" ") :: inner(tpe))
121121
case ConstantType(constant) =>
122122
plain(constant.show).l
123123
case ThisType(tpe) =>
@@ -132,7 +132,7 @@ trait TypesSupport:
132132
inner(tpe) :+ plain("*")
133133
case CapturingType(base, refs) => base match
134134
case t @ AppliedType(base, args) if t.isFunctionType =>
135-
functionType(t, base, args)(using inCC = Some(refs))
135+
functionType(base, args)(using inCC = Some(refs))
136136
case _ => inner(base) ++ renderCapturing(refs)
137137
case AnnotatedType(tpe, _) =>
138138
inner(tpe)
@@ -248,7 +248,7 @@ trait TypesSupport:
248248
++ inParens(inner(rhs), shouldWrapInParens(rhs, t, false))
249249

250250
case t @ AppliedType(tpe, args) if t.isFunctionType =>
251-
functionType(t, tpe, args)
251+
functionType(tpe, args)
252252

253253
case t @ AppliedType(tpe, typeList) =>
254254
inner(tpe) ++ plain("[").l ++ commas(typeList.map { t => t match
@@ -334,19 +334,14 @@ trait TypesSupport:
334334
s"${tpe.show(using Printer.TypeReprStructure)}"
335335
throw MatchError(msg)
336336

337-
private def functionType(using qctx: Quotes)(t: reflect.TypeRepr, tpe: reflect.TypeRepr, args: List[reflect.TypeRepr])(using
337+
private def functionType(using qctx: Quotes)(funTy: reflect.TypeRepr, args: List[reflect.TypeRepr])(using
338338
elideThis: reflect.ClassDef,
339339
indent: Int,
340340
skipTypeSuffix: Boolean,
341341
inCC: Option[List[reflect.TypeRepr]],
342342
): SSignature =
343343
import reflect._
344-
val refs = if !inCC.isDefined && t.isContextFunctionType then
345-
// This'll ensure that an impure context function type is rendered correctly
346-
Some(List(CaptureDefs.captureRoot.termRef))
347-
else
348-
inCC
349-
val arrow = plain(" ") :: (renderFunctionArrow(using qctx)(refs, t.isFunction1, t.isContextFunctionType) ++ plain(" ").l)
344+
val arrow = plain(" ") :: (renderFunctionArrow(using qctx)(funTy, inCC) ++ plain(" ").l)
350345
given Option[List[TypeRepr]] = None // FIXME: this is ugly
351346
args match
352347
case Nil => Nil
@@ -485,23 +480,29 @@ trait TypesSupport:
485480
import reflect._
486481
Keyword("^") :: renderCaptureSet(refs)
487482

488-
private def renderFunctionArrow(using Quotes)(refs: List[reflect.TypeRepr], isPureFun: Boolean, isImplicitFun: Boolean)(using elideThis: reflect.ClassDef): SSignature =
489-
import reflect._
490-
val prefix = if isImplicitFun then "?" else ""
491-
if !ccEnabled then
492-
List(Keyword(prefix + "=>"))
493-
else
494-
refs match
495-
case Nil => if isPureFun then List(Keyword(prefix + "->")) else List(Keyword(prefix + "=>"))
496-
case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>"))
497-
case refs => Keyword(prefix + "->") :: renderCaptureSet(refs)
498-
499-
private def renderFunctionArrow(using qctx: Quotes)(refs: Option[List[reflect.TypeRepr]], isPureFun: Boolean, isImplicitFun: Boolean)(using elideThis: reflect.ClassDef): SSignature =
483+
private def renderFunctionArrow(using Quotes)(funTy: reflect.TypeRepr, captures: Option[List[reflect.TypeRepr]])(using elideThis: reflect.ClassDef): SSignature =
500484
import reflect._
501-
val prefix = if isImplicitFun then "?" else ""
485+
val isContextFun = funTy.isAnyContextFunction || funTy.isAnyImpureContextFunction
486+
val prefix = if isContextFun then "?" else ""
502487
if !ccEnabled then
503488
List(Keyword(prefix + "=>"))
504489
else
505-
refs match
506-
case None => if isPureFun then List(Keyword(prefix + "->")) else List(Keyword(prefix + "=>"))
507-
case Some(refs) => renderFunctionArrow(using qctx)(refs, isPureFun, isImplicitFun)
490+
val isPureFun = funTy.isAnyFunction || funTy.isAnyContextFunction
491+
val isImpureFun = funTy.isAnyImpureFunction || funTy.isAnyImpureContextFunction
492+
captures match
493+
case None => // means an explicit retains* annotation is missing
494+
if isPureFun then
495+
List(Keyword(prefix + "->"))
496+
else if isImpureFun then
497+
List(Keyword(prefix + "=>"))
498+
else
499+
report.error(s"Cannot render function arrow: expected a (Context)Function* or Impure(Context)Function*, but got: ${funTy.show}")
500+
Nil
501+
case Some(refs) => // there is some capture set
502+
refs match
503+
case Nil => List(Keyword(prefix + "->"))
504+
case List(ref) if ref.isCaptureRoot => List(Keyword(prefix + "=>"))
505+
case refs => Keyword(prefix + "->") :: renderCaptureSet(refs)
506+
507+
private def renderByNameArrow(using Quotes)(captures: Option[List[reflect.TypeRepr]])(using elideThis: reflect.ClassDef): SSignature =
508+
renderFunctionArrow(CaptureDefs.Function1.typeRef, captures)

0 commit comments

Comments
 (0)