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
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
9 changes: 4 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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) =>
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Annotations.Annotation
import NameKinds.ContextBoundParamName
import typer.ConstFold
import reporting.trace
import util.SrcPos

import Decorators.*
import Constants.Constant
Expand Down Expand Up @@ -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] =>
Expand Down
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down
23 changes: 6 additions & 17 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down Expand Up @@ -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.
*/
Expand Down
11 changes: 8 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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>"
Expand Down
23 changes: 23 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading