Skip to content

Commit

Permalink
Use a specific NameKind for context bounds
Browse files Browse the repository at this point in the history
There are various places in the compiler and assorted tools where we assume
that an EvidenceParamName is the name of a context bound, but in practice we
also used the same NameKind in other cases such as for inferred contextual
functions. This commit cleans things up by replacing EvidenceParamName by:
- ContextBoundParamName
- ContextFunctionParamName
- CanThrowEvidenceParamName
- and the existing WildcardParamName

Note that Scala 2 also uses "evidence$" prefixes to represent context bounds,
this is why some pretty-printing code that aims to resugar context bounds
coming from both Scala 2 and 3 does a syntactic check for
`ContextBoundParamName.separator` instead of a semantic check on the NameKind
itself, this could perhaps be handled in a nicer way using unmangle in the
Scala2Unpickler.
  • Loading branch information
smarter committed Jul 24, 2023
1 parent ce1ce99 commit 81a8509
Show file tree
Hide file tree
Showing 16 changed files with 65 additions and 36 deletions.
22 changes: 14 additions & 8 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags
import Symbols._, StdNames._, Trees._, ContextOps._
import Decorators._, transform.SymUtils._
import Annotations.Annotation
import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, WildcardParamName}
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
import typer.{Namer, Checking}
import util.{Property, SourceFile, SourcePosition, Chars}
import config.Feature.{sourceVersion, migrateTo3, enabled}
Expand Down Expand Up @@ -203,10 +203,14 @@ object desugar {
else vdef1
end valDef

def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] =
for (tpt <- tpts) yield {
def makeImplicitParameters(
tpts: List[Tree], implicitFlag: FlagSet,
mkParamName: () => TermName,
forPrimaryConstructor: Boolean = false
)(using Context): List[ValDef] =
for (tpt, i) <- tpts.zipWithIndex yield {
val paramFlags: FlagSet = if (forPrimaryConstructor) LocalParamAccessor else Param
val epname = EvidenceParamName.fresh()
val epname = mkParamName()
ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | implicitFlag)
}

Expand Down Expand Up @@ -244,7 +248,9 @@ object desugar {
case ContextBounds(tbounds, cxbounds) =>
val iflag = if sourceVersion.isAtLeast(`future`) then Given else Implicit
evidenceParamBuf ++= makeImplicitParameters(
cxbounds, iflag, forPrimaryConstructor = isPrimaryConstructor)
cxbounds, iflag,
mkParamName = () => ContextBoundParamName.fresh(),
forPrimaryConstructor = isPrimaryConstructor)
tbounds
case LambdaTypeTree(tparams, body) =>
cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(body))
Expand Down Expand Up @@ -406,11 +412,11 @@ object desugar {
meth.paramss :+ evidenceParams
cpy.DefDef(meth)(paramss = paramss1)

/** The implicit evidence parameters of `meth`, as generated by `desugar.defDef` */
/** The parameters generated from the contextual bounds of `meth`, as generated by `desugar.defDef` */
private def evidenceParams(meth: DefDef)(using Context): List[ValDef] =
meth.paramss.reverse match {
case ValDefs(vparams @ (vparam :: _)) :: _ if vparam.mods.isOneOf(GivenOrImplicit) =>
vparams.takeWhile(_.name.is(EvidenceParamName))
vparams.takeWhile(_.name.is(ContextBoundParamName))
case _ =>
Nil
}
Expand Down Expand Up @@ -1585,7 +1591,7 @@ object desugar {

def makeContextualFunction(formals: List[Tree], body: Tree, erasedParams: List[Boolean])(using Context): Function = {
val mods = Given
val params = makeImplicitParameters(formals, mods)
val params = makeImplicitParameters(formals, mods, mkParamName = () => ContextFunctionParamName.fresh())
FunctionWithMods(params, body, Modifiers(mods), erasedParams)
}

Expand Down
24 changes: 23 additions & 1 deletion compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,31 @@ object NameKinds {
if (underlying.isEmpty) "$" + info.num + "$" else super.mkString(underlying, info)
}

/** The name of the term parameter generated for a context bound:
*
* def foo[T: A](...): ...
*
* becomes:
*
* def foo[T](...)(using evidence$0: A[T]): ...
*
* The "evidence$" prefix is a convention copied from Scala 2.
*/
val ContextBoundParamName: UniqueNameKind = new UniqueNameKind("evidence$")

/** The name of an inferred contextual function parameter:
*
* val x: A ?=> B = b
*
* becomes:
*
* val x: A ?=> B = (contextual$0: A) ?=> b
*/
val ContextFunctionParamName: UniqueNameKind = new UniqueNameKind("contextual$")

/** Other unique names */
val CanThrowEvidenceName: UniqueNameKind = new UniqueNameKind("canThrow$")
val TempResultName: UniqueNameKind = new UniqueNameKind("ev$")
val EvidenceParamName: UniqueNameKind = new UniqueNameKind("evidence$")
val DepParamName: UniqueNameKind = new UniqueNameKind("(param)")
val LazyImplicitName: UniqueNameKind = new UniqueNameKind("$_lazy_implicit_$")
val LazyLocalName: UniqueNameKind = new UniqueNameKind("$lzy")
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ object Scala3:

def isEmptyNumbered: Boolean =
!name.is(NameKinds.WildcardParamName)
&& !name.is(NameKinds.EvidenceParamName)
&& !name.is(NameKinds.ContextBoundParamName)
&& !name.is(NameKinds.ContextFunctionParamName)
&& { name match
case NameKinds.AnyNumberedName(nme.EMPTY, _) => true
case _ => false
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Contexts._
import Types._
import Flags._
import Mode.ImplicitsEnabled
import NameKinds.{LazyImplicitName, EvidenceParamName}
import NameKinds.{LazyImplicitName, ContextBoundParamName}
import Symbols._
import Types._
import Decorators._
Expand Down Expand Up @@ -993,7 +993,7 @@ trait Implicits:
def addendum = if (qt1 eq qt) "" else (i"\nWhere $qt is an alias of: $qt1")
i"parameter of ${qual.tpe.widen}$addendum"
case _ =>
i"${ if paramName.is(EvidenceParamName) then "an implicit parameter"
i"${ if paramName.is(ContextBoundParamName) then "a context parameter"
else s"parameter $paramName" } of $methodStr"
}

Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1735,7 +1735,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val cases1 = tree.cases.mapconserve {
case cdef @ CaseDef(pat @ Typed(Ident(nme.WILDCARD), _), _, _) =>
// case _ : T --> case evidence$n : T
cpy.CaseDef(cdef)(pat = untpd.Bind(EvidenceParamName.fresh(), pat))
cpy.CaseDef(cdef)(pat = untpd.Bind(WildcardParamName.fresh(), pat))
case cdef => cdef
}
typedMatchFinish(tree, tpd.EmptyTree, defn.ImplicitScrutineeTypeRef, cases1, pt)
Expand Down Expand Up @@ -2010,7 +2010,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def addCanThrowCapabilities(expr: untpd.Tree, cases: List[CaseDef])(using Context): untpd.Tree =
def makeCanThrow(tp: Type): untpd.Tree =
untpd.ValDef(
EvidenceParamName.fresh(),
CanThrowEvidenceName.fresh(),
untpd.TypeTree(defn.CanThrowClass.typeRef.appliedTo(tp)),
untpd.ref(defn.Compiletime_erasedValue))
.withFlags(Given | Final | Erased)
Expand Down Expand Up @@ -3756,7 +3756,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
else tree
else if wtp.isContextualMethod then
def isContextBoundParams = wtp.stripPoly match
case MethodType(EvidenceParamName(_) :: _) => true
case MethodType(ContextBoundParamName(_) :: _) => true
case _ => false
if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty
then // Under future-migration, don't infer implicit arguments yet for parameters
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/util/Signatures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ object Signatures {
(params :: rest)

def isSyntheticEvidence(name: String) =
if !name.startsWith(NameKinds.EvidenceParamName.separator) then false else
if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else
symbol.paramSymss.flatten.find(_.name.show == name).exists(_.flags.is(Flags.Implicit))

denot.info.stripPoly match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ object ScaladocCompletions:
defdef.trailingParamss.flatten.collect {
case param
if !param.symbol.isOneOf(Synthetic) &&
!param.name.is(EvidenceParamName) &&
!param.name.is(ContextBoundParamName) &&
param.symbol != extensionParam =>
param.name.show
}
Expand All @@ -121,7 +121,7 @@ object ScaladocCompletions:
case param
if !param.is(Synthetic) &&
!param.isTypeParam &&
!param.name.is(EvidenceParamName) =>
!param.name.is(ContextBoundParamName) =>
param.name.show
}
case other =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import scala.meta.pc.SymbolSearch
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.NameKinds.EvidenceParamName
import dotty.tools.dotc.core.NameKinds.ContextBoundParamName
import dotty.tools.dotc.core.NameOps.*
import dotty.tools.dotc.core.Names
import dotty.tools.dotc.core.Names.Name
Expand Down Expand Up @@ -270,7 +270,7 @@ class ShortenedTypePrinter(

lazy val implicitEvidenceParams: Set[Symbol] =
implicitParams
.filter(p => p.name.toString.startsWith(EvidenceParamName.separator))
.filter(p => p.name.toString.startsWith(ContextBoundParamName.separator))
.toSet

lazy val implicitEvidencesByTypeParam: Map[Symbol, List[String]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ trait ClassLikeSupport:
val baseTypeRepr = typeForClass(c).memberType(symbol)

def isSyntheticEvidence(name: String) =
if !name.startsWith(NameKinds.EvidenceParamName.separator) then false else
if !name.startsWith(NameKinds.ContextBoundParamName.separator) then false else
// This assumes that every parameter that starts with `evidence$` and is implicit is generated by compiler to desugar context bound.
// Howrever, this is just a heuristic, so
// `def foo[A](evidence$1: ClassTag[A]) = 1`
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/i11350.check
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
-- [E081] Type Error: tests/neg/i11350.scala:1:39 ----------------------------------------------------------------------
1 |class A1[T](action: A1[T] ?=> String = "") // error
| ^
| Could not infer type for parameter evidence$1 of anonymous function
| Could not infer type for parameter contextual$1 of anonymous function
|
| Partially inferred type for the parameter: A1[<?>]
|
| Expected type for the whole anonymous function: (A1[<?>]) ?=> String
-- [E081] Type Error: tests/neg/i11350.scala:2:39 ----------------------------------------------------------------------
2 |class A2[T](action: A1[T] ?=> String = summon[A1[T]]) // error
| ^
| Could not infer type for parameter evidence$2 of anonymous function
| Could not infer type for parameter contextual$2 of anonymous function
|
| Partially inferred type for the parameter: A1[<?>]
|
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/missing-implicit1.check
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
-- [E172] Type Error: tests/neg/missing-implicit1.scala:23:42 ----------------------------------------------------------
23 | List(1, 2, 3).traverse(x => Option(x)) // error
| ^
|No given instance of type testObjectInstance.Zip[Option] was found for an implicit parameter of method traverse in trait Traverse
|No given instance of type testObjectInstance.Zip[Option] was found for a context parameter of method traverse in trait Traverse
|
|The following import might fix the problem:
|
Expand Down
12 changes: 6 additions & 6 deletions tests/neg/missing-implicit3.check
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
-- [E172] Type Error: tests/neg/missing-implicit3.scala:13:36 ----------------------------------------------------------
13 |val sortedFoos = sort(List(new Foo)) // error
| ^
| No given instance of type ord.Ord[ord.Foo] was found for an implicit parameter of method sort in package ord.
| I found:
| No given instance of type ord.Ord[ord.Foo] was found for a context parameter of method sort in package ord.
| I found:
|
| ord.Ord.ordered[ord.Foo](/* missing */summon[ord.Foo => Comparable[? >: ord.Foo]])
| ord.Ord.ordered[ord.Foo](/* missing */summon[ord.Foo => Comparable[? >: ord.Foo]])
|
| But no implicit values were found that match type ord.Foo => Comparable[? >: ord.Foo].
| But no implicit values were found that match type ord.Foo => Comparable[? >: ord.Foo].
|
| The following import might make progress towards fixing the problem:
| The following import might make progress towards fixing the problem:
|
| import scala.math.Ordered.orderingToOrdered
| import scala.math.Ordered.orderingToOrdered
|
6 changes: 3 additions & 3 deletions tests/neg/missing-implicit4.check
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
-- [E172] Type Error: tests/neg/missing-implicit4.scala:20:42 ----------------------------------------------------------
20 | List(1, 2, 3).traverse(x => Option(x)) // error
| ^
| No given instance of type Zip[Option] was found for an implicit parameter of method traverse in trait Traverse
| No given instance of type Zip[Option] was found for a context parameter of method traverse in trait Traverse
|
| The following import might fix the problem:
| The following import might fix the problem:
|
| import instances.zipOption
| import instances.zipOption
|
2 changes: 1 addition & 1 deletion tests/run-staging/multi-staging.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
stage1 code: ((q1: scala.quoted.Quotes) ?=> {
val x1: scala.Int = 2
scala.quoted.runtime.Expr.quote[scala.Int](1.+(scala.quoted.runtime.Expr.splice[scala.Int](((evidence$5: scala.quoted.Quotes) ?=> scala.quoted.Expr.apply[scala.Int](x1)(scala.quoted.ToExpr.IntToExpr[scala.Int])(evidence$5))))).apply(using q1)
scala.quoted.runtime.Expr.quote[scala.Int](1.+(scala.quoted.runtime.Expr.splice[scala.Int](((contextual$5: scala.quoted.Quotes) ?=> scala.quoted.Expr.apply[scala.Int](x1)(scala.quoted.ToExpr.IntToExpr[scala.Int])(contextual$5))))).apply(using q1)
})
3
2 changes: 1 addition & 1 deletion tests/run-staging/quote-nested-2.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
((q: scala.quoted.Quotes) ?=> {
val a: scala.quoted.Expr[scala.Int] = scala.quoted.runtime.Expr.quote[scala.Int](4).apply(using q)
((evidence$2: scala.quoted.Quotes) ?=> a).apply(using q)
((contextual$2: scala.quoted.Quotes) ?=> a).apply(using q)
})
2 changes: 1 addition & 1 deletion tests/run-staging/quote-nested-5.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
((q: scala.quoted.Quotes) ?=> {
val a: scala.quoted.Expr[scala.Int] = scala.quoted.runtime.Expr.quote[scala.Int](4).apply(using q)
((q2: scala.quoted.Quotes) ?=> ((evidence$2: scala.quoted.Quotes) ?=> a).apply(using q2))
((q2: scala.quoted.Quotes) ?=> ((contextual$2: scala.quoted.Quotes) ?=> a).apply(using q2))
}.apply(using q))

0 comments on commit 81a8509

Please sign in to comment.