Skip to content

Commit db65435

Browse files
authored
Change into scheme to be fully type-based (#23014)
A new version of the `into` scheme to allow certain implicit conversions without requiring a language import. This is a lot simpler than previous schemes since it makes use of the power of the type system instead of building up a parallel structure based on modifiers. It is also considerably more flexible than the previous scheme. One open question might be whether it's _too_ flexible.
2 parents 713b233 + 7bcfa6c commit db65435

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+733
-466
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,8 @@ object desugar {
652652
tdef, evidenceBuf,
653653
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
654654
inventGivenName, Nil)
655+
if tdef.mods.flags.is(Into, butNot = Opaque) then
656+
report.error(ModifierNotAllowedForDefinition(Into), flagSourcePos(tdef, Into))
655657
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
656658

657659
/** The expansion of a class definition. See inline comments for what is involved */
@@ -2268,11 +2270,8 @@ object desugar {
22682270
assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode)
22692271
Select(t, op.name)
22702272
case PrefixOp(op, t) =>
2271-
if op.name == tpnme.into then
2272-
Annotated(t, New(ref(defn.IntoAnnot.typeRef), Nil :: Nil))
2273-
else
2274-
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
2275-
Select(t, nspace.UNARY_PREFIX ++ op.name)
2273+
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
2274+
Select(t, nspace.UNARY_PREFIX ++ op.name)
22762275
case ForDo(enums, body) =>
22772276
makeFor(nme.foreach, nme.foreach, enums, body) orElse tree
22782277
case ForYield(enums, body) =>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import NameKinds.ContextBoundParamName
1010
import typer.ConstFold
1111
import reporting.trace
1212
import config.Feature
13+
import util.SrcPos
1314

1415
import Decorators.*
1516
import Constants.Constant
@@ -536,6 +537,10 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
536537
if id.span == result.span.startPos => Some(result)
537538
case _ => None
538539
end ImpureByNameTypeTree
540+
541+
/** The position of the modifier associated with given flag in this definition. */
542+
def flagSourcePos(mdef: DefTree, flag: FlagSet): SrcPos =
543+
mdef.mods.mods.find(_.flags == flag).getOrElse(mdef).srcPos
539544
}
540545

541546
trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
236236

237237
case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked)
238238

239+
case class Into()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Into)
240+
239241
/** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */
240242
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
241243
}
@@ -565,12 +567,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
565567
ValDef(nme.syntheticParamName(n), if (tpt == null) TypeTree() else tpt, EmptyTree)
566568
.withFlags(flags)
567569

568-
def isInto(t: Tree)(using Context): Boolean = t match
569-
case PrefixOp(Ident(tpnme.into), _) => true
570-
case Function(_, res) => isInto(res)
571-
case Parens(t) => isInto(t)
572-
case _ => false
573-
574570
def lambdaAbstract(params: List[ValDef] | List[TypeDef], tpt: Tree)(using Context): Tree =
575571
params match
576572
case Nil => tpt

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -526,19 +526,6 @@ class CleanupRetains(using Context) extends TypeMap:
526526
RetainingType(tp, Nil, byName = annot.symbol == defn.RetainsByNameAnnot)
527527
case _ => mapOver(tp)
528528

529-
/** A typemap that follows aliases and keeps their transformed results if
530-
* there is a change.
531-
*/
532-
trait FollowAliasesMap(using Context) extends TypeMap:
533-
var follow = true // Used for debugging so that we can compare results with and w/o following.
534-
def mapFollowingAliases(t: Type): Type =
535-
val t1 = t.dealiasKeepAnnots
536-
if follow && (t1 ne t) then
537-
val t2 = apply(t1)
538-
if t2 ne t1 then t2
539-
else t
540-
else mapOver(t)
541-
542529
/** An extractor for `caps.reachCapability(ref)`, which is used to express a reach
543530
* capability as a tree in a @retains annotation.
544531
*/

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,9 @@ class Definitions {
753753
@tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder")
754754
@tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError")
755755
@tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass
756+
@tu lazy val ConversionModule : Symbol = ConversionClass.companionModule
757+
@tu lazy val ConversionModuleClass: ClassSymbol = ConversionModule.moduleClass.asClass
758+
@tu lazy val Conversion_into : Symbol = ConversionModuleClass.requiredType("into")
756759

757760
@tu lazy val StringAddClass : ClassSymbol = requiredClass("scala.runtime.StringAdd")
758761
@tu lazy val StringAdd_+ : Symbol = StringAddClass.requiredMethod(nme.raw.PLUS)
@@ -1037,8 +1040,6 @@ class Definitions {
10371040
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
10381041
@tu lazy val InferredDepFunAnnot: ClassSymbol = requiredClass("scala.caps.internal.inferredDepFun")
10391042
@tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam")
1040-
@tu lazy val IntoAnnot: ClassSymbol = requiredClass("scala.annotation.into")
1041-
@tu lazy val IntoParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into")
10421043
@tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam")
10431044
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
10441045
@tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative")
@@ -1056,6 +1057,7 @@ class Definitions {
10561057
// @tu lazy val ScalaStrictFPAnnot: ClassSymbol = requiredClass("scala.annotation.strictfp")
10571058
@tu lazy val ScalaStaticAnnot: ClassSymbol = requiredClass("scala.annotation.static")
10581059
@tu lazy val SerialVersionUIDAnnot: ClassSymbol = requiredClass("scala.SerialVersionUID")
1060+
@tu lazy val SilentIntoAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into")
10591061
@tu lazy val TailrecAnnot: ClassSymbol = requiredClass("scala.annotation.tailrec")
10601062
@tu lazy val ThreadUnsafeAnnot: ClassSymbol = requiredClass("scala.annotation.threadUnsafe")
10611063
@tu lazy val ConstructorOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.constructorOnly")
@@ -1115,7 +1117,7 @@ class Definitions {
11151117

11161118
// Set of annotations that are not printed in types except under -Yprint-debug
11171119
@tu lazy val SilentAnnots: Set[Symbol] =
1118-
Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot)
1120+
Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot, SilentIntoAnnot)
11191121

11201122
// A list of annotations that are commonly used to indicate that a field/method argument or return
11211123
// type is not null. These annotations are used by the nullification logic in JavaNullInterop to
@@ -1385,6 +1387,9 @@ class Definitions {
13851387
final def isNamedTuple_From(sym: Symbol)(using Context): Boolean =
13861388
sym.name == tpnme.From && sym.owner == NamedTupleModule.moduleClass
13871389

1390+
final def isInto(sym: Symbol)(using Context): Boolean =
1391+
sym.name == tpnme.into && sym.owner == ConversionModuleClass
1392+
13881393
private val compiletimePackageAnyTypes: Set[Name] = Set(
13891394
tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString
13901395
)

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ object Flags {
252252
/** A field generated for a primary constructor parameter (no matter if it's a 'val' or not),
253253
* or an accessor of such a field.
254254
*/
255-
val (_, ParamAccessor @ _, _) = newFlags(14, "<paramaccessor>")
255+
val (ParamAccessorOrInto @ _, ParamAccessor @ _, Into @ _) = newFlags(14, "<paramaccessor>", "into")
256256

257257
/** A value or class implementing a module */
258258
val (Module @ _, ModuleVal @ _, ModuleClass @ _) = newFlags(15, "module")
@@ -452,7 +452,7 @@ object Flags {
452452
commonFlags(Private, Protected, Final, Case, Implicit, Given, Override, JavaStatic, Transparent, Erased)
453453

454454
val TypeSourceModifierFlags: FlagSet =
455-
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open
455+
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open | Into
456456

457457
val TermSourceModifierFlags: FlagSet =
458458
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked
@@ -467,7 +467,7 @@ object Flags {
467467
* TODO: Should check that FromStartFlags do not change in completion
468468
*/
469469
val FromStartFlags: FlagSet = commonFlags(
470-
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
470+
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessorOrInto,
471471
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
472472
OuterOrCovariant, LabelOrContravariant, CaseAccessor, Tracked,
473473
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,

compiler/src/dotty/tools/dotc/core/NamerOps.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,11 @@ object NamerOps:
315315
ann.tree match
316316
case ast.tpd.WitnessNamesAnnot(witnessNames) =>
317317
addContextBoundCompanionFor(sym, witnessNames, Nil)
318+
319+
/** if `sym` is a term parameter or parameter accessor, map all occurrences of
320+
* `into[T]` in its type to `T @$into`.
321+
*/
322+
extension (tp: Type)
323+
def suppressIntoIfParam(sym: Symbol)(using Context): Type =
324+
if sym.isOneOf(TermParamOrAccessor) then TypeOps.suppressInto(tp) else tp
318325
end NamerOps

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ object StdNames {
132132
val EXCEPTION_RESULT_PREFIX: N = "exceptionResult"
133133
val EXPAND_SEPARATOR: N = str.EXPAND_SEPARATOR
134134
val IMPORT: N = "<import>"
135-
val INTO: N = "$into"
136135
val MODULE_SUFFIX: N = str.MODULE_SUFFIX
137136
val OPS_PACKAGE: N = "<special-ops>"
138137
val OVERLOADED: N = "<overloaded>"

compiler/src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import typer.ForceDegree
1818
import typer.Inferencing.*
1919
import typer.IfBottom
2020
import reporting.TestingReporter
21+
import Annotations.Annotation
2122
import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing}
2223
import CaptureSet.{IdentityCaptRefMap, VarState}
2324

@@ -944,6 +945,28 @@ object TypeOps:
944945
class StripTypeVarsMap(using Context) extends TypeMap:
945946
def apply(tp: Type) = mapOver(tp).stripTypeVar
946947

948+
/** Map no-flip covariant occurrences of `into[T]` to `T @$into` */
949+
def suppressInto(using Context) = new FollowAliasesMap:
950+
def apply(t: Type): Type = t match
951+
case AppliedType(tycon: TypeRef, arg :: Nil) if variance >= 0 && defn.isInto(tycon.symbol) =>
952+
AnnotatedType(arg, Annotation(defn.SilentIntoAnnot, util.Spans.NoSpan))
953+
case _: MatchType | _: LazyRef =>
954+
t
955+
case _ =>
956+
mapFollowingAliases(t)
957+
958+
/** Map no-flip covariant occurrences of `T @$into` to `into[T]` */
959+
def revealInto(using Context) = new FollowAliasesMap:
960+
def apply(t: Type): Type = t match
961+
case AnnotatedType(t1, ann) if variance >= 0 && ann.symbol == defn.SilentIntoAnnot =>
962+
AppliedType(
963+
defn.ConversionModule.termRef.select(defn.Conversion_into), // the external reference to the opaque type
964+
t1 :: Nil)
965+
case _: MatchType | _: LazyRef =>
966+
t
967+
case _ =>
968+
mapFollowingAliases(t)
969+
947970
/** Apply [[Type.stripTypeVar]] recursively. */
948971
def stripTypeVars(tp: Type)(using Context): Type =
949972
new StripTypeVarsMap().apply(tp)

0 commit comments

Comments
 (0)