Skip to content

Make erased capability-safe #23419

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 16 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -588,9 +588,13 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case New(_) | Closure(_, _, _) =>
Pure
case TypeApply(fn, _) =>
val sym = fn.symbol
if tree.tpe.isInstanceOf[MethodOrPoly] then exprPurity(fn)
else if fn.symbol == defn.QuotedTypeModule_of || fn.symbol == defn.Predef_classOf then Pure
else if fn.symbol == defn.Compiletime_erasedValue && tree.tpe.dealias.isInstanceOf[ConstantType] then Pure
else if sym == defn.QuotedTypeModule_of
|| sym == defn.Predef_classOf
|| sym == defn.Compiletime_erasedValue && tree.tpe.dealias.isInstanceOf[ConstantType]
|| defn.capsErasedValueMethods.contains(sym)
then Pure
else Impure
case Apply(fn, args) =>
val factorPurity = minOf(exprPurity(fn), args.map(exprPurity))
Expand Down Expand Up @@ -634,6 +638,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

def isPureBinding(tree: Tree)(using Context): Boolean = statPurity(tree) >= Pure

def isPureSyntheticCaseApply(sym: Symbol)(using Context): Boolean =
sym.isAllOf(SyntheticMethod)
&& sym.name == nme.apply
&& sym.owner.is(Module)
&& {
val cls = sym.owner.companionClass
cls.is(Case) && cls.isNoInitsRealClass
}

/** Is the application `tree` with function part `fn` known to be pure?
* Function value and arguments can still be impure.
*/
Expand All @@ -645,6 +658,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

tree.tpe.isInstanceOf[ConstantType] && tree.symbol != NoSymbol && isKnownPureOp(tree.symbol) // A constant expression with pure arguments is pure.
|| fn.symbol.isStableMember && fn.symbol.isConstructor // constructors of no-inits classes are stable
|| isPureSyntheticCaseApply(fn.symbol)

/** The purity level of this reference.
* @return
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
assert(vparams.hasSameLengthAs(tp.paramNames) && vparams.head.isTerm)
(vparams.asInstanceOf[List[TermSymbol]], remaining1)
case nil =>
(tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.erasedParams).map(valueParam), Nil)
(tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.paramErasureStatuses).map(valueParam), Nil)
val (rtp, paramss) = recur(tp.instantiate(vparams.map(_.termRef)), remaining1)
(rtp, vparams :: paramss)
case _ =>
Expand Down
8 changes: 3 additions & 5 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -462,15 +462,13 @@ extension (sym: Symbol)

/** Does this symbol allow results carrying the universal capability?
* Currently this is true only for function type applies (since their
* results are unboxed) and `erasedValue` since this function is magic in
* that is allows to conjure global capabilies from nothing (aside: can we find a
* more controlled way to achieve this?).
* results are unboxed) and `caps.{$internal,unsafe}.erasedValue` since
* these function are magic in that they allow to conjure global capabilies from nothing.
* But it could be generalized to other functions that so that they can take capability
* classes as arguments.
*/
def allowsRootCapture(using Context): Boolean =
sym == defn.Compiletime_erasedValue
|| defn.isFunctionClass(sym.maybeOwner)
defn.capsErasedValueMethods.contains(sym) || defn.isFunctionClass(sym.maybeOwner)

/** When applying `sym`, would the result type be unboxed?
* This is the case if the result type contains a top-level reference to an enclosing
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ class CheckCaptures extends Recheck, SymTransformer:
* @param args the type arguments
*/
def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit =
def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue
def isExempt = sym.isTypeTestOrCast || defn.capsErasedValueMethods.contains(sym)
if !isExempt then
val paramNames = atPhase(thisPhase.prev):
fn.tpe.widenDealias match
Expand Down
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ object CheckRealizable {

def boundsRealizability(tp: Type)(using Context): Realizability =
new CheckRealizable().boundsRealizability(tp)

private val LateInitializedFlags = Lazy | Erased
}

/** Compute realizability status.
Expand All @@ -72,7 +70,7 @@ class CheckRealizable(using Context) {
/** Is symbol's definitition a lazy or erased val?
* (note we exclude modules here, because their realizability is ensured separately)
*/
private def isLateInitialized(sym: Symbol) = sym.isOneOf(LateInitializedFlags, butNot = Module)
private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module)

/** The realizability status of given type `tp`*/
def realizability(tp: Type): Realizability = tp.dealias match {
Expand Down Expand Up @@ -184,7 +182,7 @@ class CheckRealizable(using Context) {
private def memberRealizability(tp: Type) = {
def checkField(sofar: Realizability, fld: SingleDenotation): Realizability =
sofar andAlso {
if (checkedFields.contains(fld.symbol) || fld.symbol.isOneOf(Private | Mutable | LateInitializedFlags))
if (checkedFields.contains(fld.symbol) || fld.symbol.isOneOf(Private | Mutable | Lazy))
// if field is private it cannot be part of a visible path
// if field is mutable it cannot be part of a path
// if field is lazy or erased it does not need to be initialized when the owning object is
Expand Down
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1004,9 +1004,11 @@ class Definitions {
@tu lazy val Caps_Capability: ClassSymbol = requiredClass("scala.caps.Capability")
@tu lazy val Caps_CapSet: ClassSymbol = requiredClass("scala.caps.CapSet")
@tu lazy val CapsInternalModule: Symbol = requiredModule("scala.caps.internal")
@tu lazy val Caps_erasedValue: Symbol = CapsInternalModule.requiredMethod("erasedValue")
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
@tu lazy val Caps_unsafeAssumeSeparate: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumeSeparate")
@tu lazy val Caps_unsafeErasedValue: Symbol = CapsUnsafeModule.requiredMethod("unsafeErasedValue")
@tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains")
@tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains")
@tu lazy val Caps_containsImpl: TermSymbol = Caps_ContainsModule.requiredMethod("containsImpl")
Expand Down Expand Up @@ -1558,6 +1560,11 @@ class Definitions {
@tu lazy val pureSimpleClasses =
Set(StringClass, NothingClass, NullClass) ++ ScalaValueClasses()

@tu lazy val capsErasedValueMethods =
Set(Caps_erasedValue, Caps_unsafeErasedValue)
@tu lazy val erasedValueMethods =
capsErasedValueMethods + Compiletime_erasedValue

@tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0).asInstanceOf[Array[TypeRef]]
val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass))
def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n)
Expand Down Expand Up @@ -2001,7 +2008,9 @@ class Definitions {

/** A allowlist of Scala-2 classes that are known to be pure */
def isAssuredNoInits(sym: Symbol): Boolean =
(sym `eq` SomeClass) || isTupleClass(sym)
(sym `eq` SomeClass)
|| isTupleClass(sym)
|| sym.is(Module) && isAssuredNoInits(sym.companionClass)

/** If `cls` is Tuple1..Tuple22, add the corresponding *: type as last parent to `parents` */
def adjustForTuple(cls: ClassSymbol, tparams: List[TypeSymbol], parents: List[Type]): List[Type] = {
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,7 @@ object SymDenotations {
def isEffectivelyErased(using Context): Boolean =
isOneOf(EffectivelyErased)
|| is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot)
|| defn.erasedValueMethods.contains(symbol)

/** Is this a member that will become public in the generated binary */
def hasPublicInBinary(using Context): Boolean =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2387,7 +2387,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
formals2.isEmpty
}
// If methods have erased parameters, then the erased parameters must match
val erasedValid = (!tp1.hasErasedParams && !tp2.hasErasedParams) || (tp1.erasedParams == tp2.erasedParams)
val erasedValid = (!tp1.hasErasedParams && !tp2.hasErasedParams) || (tp1.paramErasureStatuses == tp2.paramErasureStatuses)

erasedValid && loop(tp1.paramInfos, tp2.paramInfos)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
val (names, formals0) = if tp.hasErasedParams then
tp.paramNames
.zip(tp.paramInfos)
.zip(tp.erasedParams)
.zip(tp.paramErasureStatuses)
.collect{ case (param, isErased) if !isErased => param }
.unzip
else (tp.paramNames, tp.paramInfos)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3931,7 +3931,7 @@ object Types extends TypeUtils {
case tp: MethodType =>
val params = if (hasErasedParams)
tp.paramInfos
.zip(tp.erasedParams)
.zip(tp.paramErasureStatuses)
.collect { case (param, isErased) if !isErased => param }
else tp.paramInfos
resultSignature.prependTermParams(params, sourceLanguage)
Expand Down Expand Up @@ -4163,7 +4163,7 @@ object Types extends TypeUtils {
final override def isContextualMethod: Boolean =
companion.eq(ContextualMethodType)

def erasedParams(using Context): List[Boolean] =
def paramErasureStatuses(using Context): List[Boolean] =
paramInfos.map(p => p.hasAnnotation(defn.ErasedParamAnnot))

def nonErasedParamCount(using Context): Int =
Expand Down
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/inlines/InlineReducer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,17 @@ class InlineReducer(inliner: Inliner)(using Context):
val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm
val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee))

// If scrutinee has embedded `compiletime.erasedValue[T]` expressions, convert them to
// mark scrutineeSym as Erased. This means that the scrutinee cannot be referenced in
// the reduced term. It is NOT checked that scrutinee is a pure expression, since
// there is a special case in Erase that exempts the RHS of an erased scrutinee definition.
if scrutinee.existsSubTree:
case tree @ TypeApply(fn, args) => tree.symbol == defn.Compiletime_erasedValue
case _ => false
then
scrutineeSym.setFlag(Erased)


def reduceCase(cdef: CaseDef): MatchReduxWithGuard = {
val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]()

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/quoted/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ class Interpreter(pos: SrcPos, classLoader0: ClassLoader)(using Context):
case fnType: MethodType =>
val argTypes = fnType.paramInfos
assert(argss.head.size == argTypes.size)
val nonErasedArgs = argss.head.lazyZip(fnType.erasedParams).collect { case (arg, false) => arg }.toList
val nonErasedArgTypes = fnType.paramInfos.lazyZip(fnType.erasedParams).collect { case (arg, false) => arg }.toList
val nonErasedArgs = argss.head.lazyZip(fnType.paramErasureStatuses).collect { case (arg, false) => arg }.toList
val nonErasedArgTypes = fnType.paramInfos.lazyZip(fnType.paramErasureStatuses).collect { case (arg, false) => arg }.toList
assert(nonErasedArgs.size == nonErasedArgTypes.size)
interpretArgsGroup(nonErasedArgs, nonErasedArgTypes) ::: interpretArgs(argss.tail, fnType.resType)
case fnType: AppliedType if defn.isContextFunctionType(fnType) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212
case PointlessAppliedConstructorTypeID // errorNumber: 213
case IllegalContextBoundsID // errorNumber: 214
case ErasedNotPureID // errornumber 215

def errorNumber = ordinal - 1

Expand Down
32 changes: 32 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3535,3 +3535,35 @@ final class IllegalContextBounds(using Context) extends SyntaxMsg(IllegalContext
override protected def explain(using Context): String = ""

end IllegalContextBounds

final class ErasedNotPure(tree: tpd.Tree, isArgument: Boolean, isImplicit: Boolean)(using Context) extends TypeMsg(ErasedNotPureID):
def what =
if isArgument then s"${if isImplicit then "implicit " else ""}argument to an erased parameter"
else "right-hand-side of an erased value"
override protected def msg(using Context): String =
i"$what fails to be a pure expression"

override protected def explain(using Context): String =
def alternatives =
if tree.symbol == defn.Compiletime_erasedValue then
i"""An accepted (but unsafe) alternative for this expression uses function
|
| caps.unsafe.unsafeErasedValue
|
|instead."""
else
"""A pure expression is an expression that is clearly side-effect free and terminating.
|Some examples of pure expressions are:
| - literals,
| - references to values,
| - side-effect-free instance creations,
| - applications of inline functions to pure arguments."""

i"""The $what must be a pure expression, but I found:
|
| $tree
|
|This expression is not classified to be pure.
|$alternatives"""
end ErasedNotPure

23 changes: 19 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import core.Names.*
import core.StdNames.*
import core.NameOps.*
import core.Periods.currentStablePeriod
import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName}
import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName, InlineScrutineeName}
import core.Scopes.newScopeWith
import core.Decorators.*
import core.Constants.*
Expand Down Expand Up @@ -583,6 +583,12 @@ object Erasure {
checkNotErasedClass(tree)
end checkNotErased

def checkPureErased(tree: untpd.Tree, isArgument: Boolean, isImplicit: Boolean = false)(using Context): Unit =
val tree1 = tree.asInstanceOf[tpd.Tree]
inContext(preErasureCtx):
if !tpd.isPureExpr(tree1) then
report.error(ErasedNotPure(tree1, isArgument, isImplicit), tree1.srcPos)

private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match
case JavaArrayType(et) =>
checkNotErasedClass(et, tree)
Expand Down Expand Up @@ -848,7 +854,13 @@ object Erasure {
val origFunType = origFun.tpe.widen(using preErasureCtx)
val ownArgs = origFunType match
case mt: MethodType if mt.hasErasedParams =>
args.zip(mt.erasedParams).collect { case (arg, false) => arg }
args.lazyZip(mt.paramErasureStatuses).flatMap: (arg, isErased) =>
if isErased then
checkPureErased(arg, isArgument = true,
isImplicit = mt.isImplicitMethod && arg.span.isSynthetic)
Nil
else
arg :: Nil
case _ => args
val fun1 = typedExpr(fun, AnyFunctionProto)
fun1.tpe.widen match
Expand Down Expand Up @@ -916,8 +928,11 @@ object Erasure {
}

override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree =
if (sym.isEffectivelyErased) erasedDef(sym)
else
if sym.isEffectivelyErased then
if !sym.name.is(InlineScrutineeName) then
checkPureErased(vdef.rhs, isArgument = false)
erasedDef(sym)
else trace(i"erasing $vdef"):
checkNotErasedClass(sym.info, vdef)
super.typedValDef(untpd.cpy.ValDef(vdef)(
tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Getters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class Getters extends MiniPhase with SymTransformer { thisPhase =>
d1
}

private val NoGetterNeededFlags = Method | Param | JavaDefined | JavaStatic | PhantomSymbol
private val NoGetterNeededFlags = Method | Param | JavaDefined | JavaStatic | PhantomSymbol | Erased

val newSetters = util.HashSet[Symbol]()

Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
case app: Apply =>
val methType = app.fun.tpe.widen.asInstanceOf[MethodType]
if (methType.hasErasedParams)
for (arg, isErased) <- app.args.lazyZip(methType.erasedParams) do
for (arg, isErased) <- app.args.lazyZip(methType.paramErasureStatuses) do
if isErased then
if methType.isResultDependent then
Checking.checkRealizable(arg.tpe, arg.srcPos, "erased argument")
Expand Down Expand Up @@ -475,15 +475,15 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
case tree: ValDef =>
annotateExperimentalCompanion(tree.symbol)
registerIfHasMacroAnnotations(tree)
checkErasedDef(tree)
//checkErasedDef(tree)
Checking.checkPolyFunctionType(tree.tpt)
val tree1 = cpy.ValDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt))
if tree1.removeAttachment(desugar.UntupledParam).isDefined then
checkStableSelection(tree.rhs)
processValOrDefDef(super.transform(tree1))
case tree: DefDef =>
registerIfHasMacroAnnotations(tree)
checkErasedDef(tree)
//checkErasedDef(tree)
Checking.checkPolyFunctionType(tree.tpt)
annotateContextResults(tree)
val tree1 = cpy.DefDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt))
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,12 @@ object Checking {
fail(ModifierNotAllowedForDefinition(Flags.Infix, s"A top-level ${sym.showKind} cannot be infix."))
if sym.isUpdateMethod && !sym.owner.derivesFrom(defn.Caps_Mutable) then
fail(em"Update methods can only be used as members of classes extending the `Mutable` trait")
checkApplicable(Erased,
!sym.is(Lazy, butNot = Given)
&& !sym.isMutableVarOrAccessor
&& (!sym.isType || sym.isClass))
val unerasable =
sym.is(Lazy, butNot = Given)
|| sym.is(Method, butNot = Macro)
|| sym.is(Mutable)
|| sym.isType && !sym.isClass
checkApplicable(Erased, !unerasable)
checkCombination(Final, Open)
checkCombination(Sealed, Open)
checkCombination(Final, Sealed)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ object EtaExpansion extends LiftImpure {
val body = Apply(lifted, ids)
if (mt.isContextualMethod) body.setApplyKind(ApplyKind.Using)
val fn =
if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given), mt.erasedParams)
else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit), mt.erasedParams)
else if (mt.hasErasedParams) new untpd.FunctionWithMods(params, body, Modifiers(), mt.erasedParams)
if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given), mt.paramErasureStatuses)
else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit), mt.paramErasureStatuses)
else if (mt.hasErasedParams) new untpd.FunctionWithMods(params, body, Modifiers(), mt.paramErasureStatuses)
else untpd.Function(params, body)
if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
case PreciseConstrained(tp, true) =>
if tp.isSingletonBounded(frozen = false) then
withNoErrors:
ref(defn.Compiletime_erasedValue).appliedToType(formal).withSpan(span)
ref(defn.Caps_erasedValue).appliedToType(formal).withSpan(span)
else
withErrors(i"$tp is not a singleton")
case _ =>
Expand All @@ -240,7 +240,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
val synthesizedPrecise: SpecialHandler = (formal, span) => formal match
case PreciseConstrained(tp, false) =>
withNoErrors:
ref(defn.Compiletime_erasedValue).appliedToType(formal).withSpan(span)
ref(defn.Caps_erasedValue).appliedToType(formal).withSpan(span)
case _ =>
EmptyTreeNoError

Expand Down
Loading
Loading