Skip to content

Change into scheme to be fully type-based #23014

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 18 commits into
base: main
Choose a base branch
from
Open
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
9 changes: 4 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
@@ -652,6 +652,8 @@ object desugar {
tdef, evidenceBuf,
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
inventGivenName, Nil)
if tdef.mods.flags.is(Into, butNot = Opaque) then
report.error(ModifierNotAllowedForDefinition(Into), flagSourcePos(tdef, Into))
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)

/** The expansion of a class definition. See inline comments for what is involved */
@@ -2268,11 +2270,8 @@ object desugar {
assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode)
Select(t, op.name)
case PrefixOp(op, t) =>
if op.name == tpnme.into then
Annotated(t, New(ref(defn.IntoAnnot.typeRef), Nil :: Nil))
else
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
Select(t, nspace.UNARY_PREFIX ++ op.name)
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
Select(t, nspace.UNARY_PREFIX ++ op.name)
case ForDo(enums, body) =>
makeFor(nme.foreach, nme.foreach, enums, body) orElse tree
case ForYield(enums, body) =>
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import Annotations.Annotation
import NameKinds.ContextBoundParamName
import typer.ConstFold
import reporting.trace
import util.SrcPos

import Decorators.*
import Constants.Constant
@@ -522,6 +523,10 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
if id.span == result.span.startPos => Some(result)
case _ => None
end ImpureByNameTypeTree

/** The position of the modifier associated with given flag in this definition. */
def flagSourcePos(mdef: DefTree, flag: FlagSet): SrcPos =
mdef.mods.mods.find(_.flags == flag).getOrElse(mdef).srcPos
}

trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
@@ -236,6 +236,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

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

case class Into()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Into)

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

def isInto(t: Tree)(using Context): Boolean = t match
case PrefixOp(Ident(tpnme.into), _) => true
case Function(_, res) => isInto(res)
case Parens(t) => isInto(t)
case _ => false

def lambdaAbstract(params: List[ValDef] | List[TypeDef], tpt: Tree)(using Context): Tree =
params match
case Nil => tpt
23 changes: 6 additions & 17 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
@@ -104,10 +104,12 @@ extension (tp: Type)
final def isTrackableRef(using Context): Boolean = tp match
case _: (ThisType | TermParamRef) => true
case tp: TermRef =>
((tp.prefix eq NoPrefix)
|| tp.symbol.isField && !tp.symbol.isStatic && tp.prefix.isTrackableRef
|| tp.isCap
) && !tp.symbol.isOneOf(UnstableValueFlags)
!tp.underlying.exists // might happen during construction of lambdas with annotations on parameters
||
((tp.prefix eq NoPrefix)
|| tp.symbol.isField && !tp.symbol.isStatic && tp.prefix.isTrackableRef
|| tp.isCap
) && !tp.symbol.isOneOf(UnstableValueFlags)
case tp: TypeRef =>
tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet)
case tp: TypeParamRef =>
@@ -637,19 +639,6 @@ class CleanupRetains(using Context) extends TypeMap:
RetainingType(tp, Nil, byName = annot.symbol == defn.RetainsByNameAnnot)
case _ => mapOver(tp)

/** A typemap that follows aliases and keeps their transformed results if
* there is a change.
*/
trait FollowAliasesMap(using Context) extends TypeMap:
var follow = true // Used for debugging so that we can compare results with and w/o following.
def mapFollowingAliases(t: Type): Type =
val t1 = t.dealiasKeepAnnots
if follow && (t1 ne t) then
val t2 = apply(t1)
if t2 ne t1 then t2
else t
else mapOver(t)

/** An extractor for `caps.reachCapability(ref)`, which is used to express a reach
* capability as a tree in a @retains annotation.
*/
11 changes: 8 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
@@ -753,6 +753,9 @@ class Definitions {
@tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder")
@tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError")
@tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass
@tu lazy val ConversionModule : Symbol = ConversionClass.companionModule
@tu lazy val ConversionModuleClass: ClassSymbol = ConversionModule.moduleClass.asClass
@tu lazy val Conversion_into : Symbol = ConversionModuleClass.requiredType("into")

@tu lazy val StringAddClass : ClassSymbol = requiredClass("scala.runtime.StringAdd")
@tu lazy val StringAdd_+ : Symbol = StringAddClass.requiredMethod(nme.raw.PLUS)
@@ -1039,8 +1042,6 @@ class Definitions {
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
@tu lazy val InlineParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InlineParam")
@tu lazy val IntoAnnot: ClassSymbol = requiredClass("scala.annotation.into")
@tu lazy val IntoParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into")
@tu lazy val ErasedParamAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ErasedParam")
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
@tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative")
@@ -1058,6 +1059,7 @@ class Definitions {
// @tu lazy val ScalaStrictFPAnnot: ClassSymbol = requiredClass("scala.annotation.strictfp")
@tu lazy val ScalaStaticAnnot: ClassSymbol = requiredClass("scala.annotation.static")
@tu lazy val SerialVersionUIDAnnot: ClassSymbol = requiredClass("scala.SerialVersionUID")
@tu lazy val SilentIntoAnnot: ClassSymbol = requiredClass("scala.annotation.internal.$into")
@tu lazy val TailrecAnnot: ClassSymbol = requiredClass("scala.annotation.tailrec")
@tu lazy val ThreadUnsafeAnnot: ClassSymbol = requiredClass("scala.annotation.threadUnsafe")
@tu lazy val ConstructorOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.constructorOnly")
@@ -1117,7 +1119,7 @@ class Definitions {

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

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

final def isInto(sym: Symbol)(using Context): Boolean =
sym.name == tpnme.into && sym.owner == ConversionModuleClass

private val compiletimePackageAnyTypes: Set[Name] = Set(
tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString
)
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
@@ -252,7 +252,7 @@ object Flags {
/** A field generated for a primary constructor parameter (no matter if it's a 'val' or not),
* or an accessor of such a field.
*/
val (_, ParamAccessor @ _, _) = newFlags(14, "<paramaccessor>")
val (ParamAccessorOrInto @ _, ParamAccessor @ _, Into @ _) = newFlags(14, "<paramaccessor>", "into")

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

val TypeSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open | Into

val TermSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked
@@ -467,7 +467,7 @@ object Flags {
* TODO: Should check that FromStartFlags do not change in completion
*/
val FromStartFlags: FlagSet = commonFlags(
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessorOrInto,
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
OuterOrCovariant, LabelOrContravariant, CaseAccessor, Tracked,
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
@@ -317,4 +317,11 @@ object NamerOps:
ann.tree match
case ast.tpd.WitnessNamesAnnot(witnessNames) =>
addContextBoundCompanionFor(sym, witnessNames, Nil)

/** if `sym` is a term parameter or parameter accessor, map all occurrences of
* `into[T]` in its type to `T @$into`.
*/
extension (tp: Type)
def suppressIntoIfParam(sym: Symbol)(using Context): Type =
if sym.isOneOf(TermParamOrAccessor) then TypeOps.suppressInto(tp) else tp
end NamerOps
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
@@ -132,7 +132,6 @@ object StdNames {
val EXCEPTION_RESULT_PREFIX: N = "exceptionResult"
val EXPAND_SEPARATOR: N = str.EXPAND_SEPARATOR
val IMPORT: N = "<import>"
val INTO: N = "$into"
val MODULE_SUFFIX: N = str.MODULE_SUFFIX
val OPS_PACKAGE: N = "<special-ops>"
val OVERLOADED: N = "<overloaded>"
23 changes: 23 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import typer.ForceDegree
import typer.Inferencing.*
import typer.IfBottom
import reporting.TestingReporter
import Annotations.Annotation
import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing}
import CaptureSet.{CompareResult, IdentityCaptRefMap, VarState}

@@ -936,6 +937,28 @@ object TypeOps:
class StripTypeVarsMap(using Context) extends TypeMap:
def apply(tp: Type) = mapOver(tp).stripTypeVar

/** Map no-flip covariant occurrences of `into[T]` to `T @$into` */
def suppressInto(using Context) = new FollowAliasesMap:
def apply(t: Type): Type = t match
case AppliedType(tycon: TypeRef, arg :: Nil) if variance >= 0 && defn.isInto(tycon.symbol) =>
AnnotatedType(arg, Annotation(defn.SilentIntoAnnot, util.Spans.NoSpan))
case _: MatchType | _: LazyRef =>
t
case _ =>
mapFollowingAliases(t)

/** Map no-flip covariant occurrences of `T @$into` to `into[T]` */
def revealInto(using Context) = new FollowAliasesMap:
def apply(t: Type): Type = t match
case AnnotatedType(t1, ann) if variance >= 0 && ann.symbol == defn.SilentIntoAnnot =>
AppliedType(
defn.ConversionModule.termRef.select(defn.Conversion_into), // the external reference to the opaque type
t1 :: Nil)
case _: MatchType | _: LazyRef =>
t
case _ =>
mapFollowingAliases(t)

/** Apply [[Type.stripTypeVar]] recursively. */
def stripTypeVars(tp: Type)(using Context): Type =
new StripTypeVarsMap().apply(tp)
Loading