Skip to content
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

Implement into modifier on parameter types #14514

Merged
merged 4 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 27 additions & 17 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import core._
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 typer.{Namer, Checking}
import util.{Property, SourceFile, SourcePosition, Chars}
Expand Down Expand Up @@ -165,32 +166,41 @@ object desugar {
*
* Generate setter where needed
*/
def valDef(vdef0: ValDef)(using Context): Tree = {
def valDef(vdef0: ValDef)(using Context): Tree =
val vdef @ ValDef(_, tpt, rhs) = vdef0
val mods = vdef.mods

val valName = normalizeName(vdef, tpt).asTermName
val vdef1 = cpy.ValDef(vdef)(name = valName)
var mods1 = vdef.mods

def dropInto(tpt: Tree): Tree = tpt match
case Into(tpt1) =>
mods1 = vdef.mods.withAddedAnnotation(
TypedSplice(
Annotation(defn.AllowConversionsAnnot).tree.withSpan(tpt.span.startPos)))
tpt1
case ByNameTypeTree(tpt1) =>
cpy.ByNameTypeTree(tpt)(dropInto(tpt1))
case PostfixOp(tpt1, op) if op.name == tpnme.raw.STAR =>
cpy.PostfixOp(tpt)(dropInto(tpt1), op)
case _ =>
tpt

val vdef1 = cpy.ValDef(vdef)(name = valName, tpt = dropInto(tpt))
.withMods(mods1)

if (isSetterNeeded(vdef)) {
// TODO: copy of vdef as getter needed?
// val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos?
// right now vdef maps via expandedTree to a thicket which concerns itself.
// I don't see a problem with that but if there is one we can avoid it by making a copy here.
if isSetterNeeded(vdef) then
val setterParam = makeSyntheticParameter(tpt = SetterParamTree().watching(vdef))
// The rhs gets filled in later, when field is generated and getter has parameters (see Memoize miniphase)
val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral
val setter = cpy.DefDef(vdef)(
name = valName.setterName,
paramss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
.dropEndMarker() // the end marker should only appear on the getter definition
name = valName.setterName,
paramss = (setterParam :: Nil) :: Nil,
tpt = TypeTree(defn.UnitType),
rhs = setterRhs
).withMods((vdef.mods | Accessor) &~ (CaseAccessor | GivenOrImplicit | Lazy))
.dropEndMarker() // the end marker should only appear on the getter definition
Thicket(vdef1, setter)
}
else vdef1
}
end valDef

def makeImplicitParameters(tpts: List[Tree], implicitFlag: FlagSet, forPrimaryConstructor: Boolean = false)(using Context): List[ValDef] =
for (tpt <- tpts) yield {
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
case class Into(tpt: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree

case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
Expand Down Expand Up @@ -649,6 +650,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match
case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree
case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source))
def Into(tree: Tree)(tpt: Tree)(using Context): Tree = tree match
case tree: Into if tpt eq tree.tpt => tree
case _ => finalize(tree, untpd.Into(tpt)(tree.source))
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match {
case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree
case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source))
Expand Down Expand Up @@ -718,6 +722,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
case ExtMethods(paramss, methods) =>
cpy.ExtMethods(tree)(transformParamss(paramss), transformSub(methods))
case Into(tpt) =>
cpy.Into(tree)(transform(tpt))
case ImportSelector(imported, renamed, bound) =>
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
case Number(_, _) | TypedSplice(_) =>
Expand Down Expand Up @@ -777,6 +783,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
this(this(this(x, pats), tpt), rhs)
case ExtMethods(paramss, methods) =>
this(paramss.foldLeft(x)(apply), methods)
case Into(tpt) =>
this(x, tpt)
case ImportSelector(imported, renamed, bound) =>
this(this(this(x, imported), renamed), bound)
case Number(_, _) =>
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ object Feature:
val saferExceptions = experimental("saferExceptions")
val pureFunctions = experimental("pureFunctions")
val captureChecking = experimental("captureChecking")
val into = experimental("into")

val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)

Expand Down
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ object Decorators {
/** Implements filterConserve, zipWithConserve methods
* on lists that avoid duplication of list nodes where feasible.
*/
implicit class ListDecorator[T](val xs: List[T]) extends AnyVal {
extension [T](xs: List[T])

final def mapconserve[U](f: T => U): List[U] = {
@tailrec
Expand Down Expand Up @@ -207,11 +207,7 @@ object Decorators {
}

/** Union on lists seen as sets */
def | (ys: List[T]): List[T] = xs ::: (ys filterNot (xs contains _))

/** Intersection on lists seen as sets */
def & (ys: List[T]): List[T] = xs filter (ys contains _)
}
def setUnion (ys: List[T]): List[T] = xs ::: ys.filterNot(xs contains _)

extension [T, U](xss: List[List[T]])
def nestedMap(f: T => U): List[List[U]] = xss match
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,8 @@ class Definitions {

@tu lazy val RepeatedParamClass: ClassSymbol = enterSpecialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType))

@tu lazy val IntoType: TypeSymbol = enterAliasType(tpnme.INTO, HKTypeLambda(TypeBounds.empty :: Nil)(_.paramRefs(0)))

// fundamental classes
@tu lazy val StringClass: ClassSymbol = requiredClass("java.lang.String")
def StringType: Type = StringClass.typeRef
Expand Down Expand Up @@ -973,6 +975,7 @@ class Definitions {
@tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation")

// Annotation classes
@tu lazy val AllowConversionsAnnot: ClassSymbol = requiredClass("scala.annotation.allowConversions")
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = requiredClass("scala.annotation.internal.AnnotationDefault")
@tu lazy val BeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BeanProperty")
@tu lazy val BooleanBeanPropertyAnnot: ClassSymbol = requiredClass("scala.beans.BooleanBeanProperty")
Expand Down Expand Up @@ -2005,6 +2008,7 @@ class Definitions {
orType,
RepeatedParamClass,
ByNameParamClass2x,
IntoType,
AnyValClass,
NullClass,
NothingClass,
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import scala.annotation.internal.sharable
object Names {
import NameKinds._

/** Things that can be turned into names with `totermName` and `toTypeName`
* Decorators defines implements these as extension methods for strings.
/** Things that can be turned into names with `toTermName` and `toTypeName`.
* Decorators implements these as extension methods for strings.
*/
type PreName = Name | String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
case param: TypeParamRef if contains(param) =>
param :: (if (isUpper) upper(param) else lower(param))
case tp: AndType if isUpper =>
dependentParams(tp.tp1, isUpper) | (dependentParams(tp.tp2, isUpper))
dependentParams(tp.tp1, isUpper).setUnion(dependentParams(tp.tp2, isUpper))
case tp: OrType if !isUpper =>
dependentParams(tp.tp1, isUpper).intersect(dependentParams(tp.tp2, isUpper))
case EtaExpansion(tycon) =>
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ 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 Expand Up @@ -500,6 +501,7 @@ object StdNames {
val info: N = "info"
val inlinedEquals: N = "inlinedEquals"
val internal: N = "internal"
val into: N = "into"
val isArray: N = "isArray"
val isDefinedAt: N = "isDefinedAt"
val isDefinedAtImpl: N = "$isDefinedAt"
Expand Down
60 changes: 45 additions & 15 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ object Types {
def isRepeatedParam(using Context): Boolean =
typeSymbol eq defn.RepeatedParamClass

/** Is this a parameter type that allows implicit argument converson? */
def isConvertibleParam(using Context): Boolean =
typeSymbol eq defn.IntoType

/** Is this the type of a method that has a repeated parameter type as
* last parameter type?
*/
Expand Down Expand Up @@ -536,7 +540,7 @@ object Types {
case tp: ClassInfo =>
tp.cls :: Nil
case AndType(l, r) =>
l.parentSymbols(include) | r.parentSymbols(include)
l.parentSymbols(include).setUnion(r.parentSymbols(include))
case OrType(l, r) =>
l.parentSymbols(include) intersect r.parentSymbols(include) // TODO does not conform to spec
case _ =>
Expand Down Expand Up @@ -1864,6 +1868,11 @@ object Types {

def dropRepeatedAnnot(using Context): Type = dropAnnot(defn.RepeatedAnnot)

/** A translation from types of original parameter ValDefs to the types
* of parameters in MethodTypes.
* Translates `Seq[T] @repeated` or `Array[T] @repeated` to `<repeated>[T]`.
* That way, repeated arguments are made manifest without risk of dropped annotations.
*/
def annotatedToRepeated(using Context): Type = this match {
case tp @ ExprType(tp1) =>
tp.derivedExprType(tp1.annotatedToRepeated)
Expand Down Expand Up @@ -3948,27 +3957,48 @@ object Types {
* and inline parameters:
* - replace @repeated annotations on Seq or Array types by <repeated> types
* - add @inlineParam to inline parameters
* - add @erasedParam to erased parameters
* - wrap types of parameters that have an @allowConversions annotation with Into[_]
*/
def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = {
def translateInline(tp: Type): Type = tp match {
case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.InlineParamAnnot)))
case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot))
}
def translateErased(tp: Type): Type = tp match {
case ExprType(resType) => ExprType(AnnotatedType(resType, Annotation(defn.ErasedParamAnnot)))
case _ => AnnotatedType(tp, Annotation(defn.ErasedParamAnnot))
}
def paramInfo(param: Symbol) = {
def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType =
def addAnnotation(tp: Type, cls: ClassSymbol): Type = tp match
case ExprType(resType) => ExprType(addAnnotation(resType, cls))
case _ => AnnotatedType(tp, Annotation(cls))

def wrapConvertible(tp: Type) =
AppliedType(defn.IntoType.typeRef, tp :: Nil)

/** Add `Into[..] to the type itself and if it is a function type, to all its
* curried result type(s) as well.
*/
def addInto(tp: Type): Type = tp match
case tp @ AppliedType(tycon, args) if tycon.typeSymbol == defn.RepeatedParamClass =>
tp.derivedAppliedType(tycon, addInto(args.head) :: Nil)
case tp @ AppliedType(tycon, args) if defn.isFunctionType(tp) =>
KacperFKorban marked this conversation as resolved.
Show resolved Hide resolved
wrapConvertible(tp.derivedAppliedType(tycon, args.init :+ addInto(args.last)))
case tp @ RefinedType(parent, rname, rinfo) if defn.isFunctionOrPolyType(tp) =>
wrapConvertible(tp.derivedRefinedType(parent, rname, addInto(rinfo)))
case tp: MethodOrPoly =>
tp.derivedLambdaType(resType = addInto(tp.resType))
case ExprType(resType) =>
ExprType(addInto(resType))
case _ =>
wrapConvertible(tp)

def paramInfo(param: Symbol) =
var paramType = param.info.annotatedToRepeated
if (param.is(Inline)) paramType = translateInline(paramType)
if (param.is(Erased)) paramType = translateErased(paramType)
if param.is(Inline) then
paramType = addAnnotation(paramType, defn.InlineParamAnnot)
if param.is(Erased) then
paramType = addAnnotation(paramType, defn.ErasedParamAnnot)
if param.hasAnnotation(defn.AllowConversionsAnnot) then
paramType = addInto(paramType)
paramType
}

apply(params.map(_.name.asTermName))(
tl => params.map(p => tl.integrate(params, paramInfo(p))),
tl => tl.integrate(params, resultType))
}
end fromSymbols

final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType =
checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self)))
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,13 @@ object Parsers {
else
core()

private def maybeInto(tp: () => Tree) =
if in.isIdent(nme.into)
&& in.featureEnabled(Feature.into)
&& canStartTypeTokens.contains(in.lookahead.token)
then atSpan(in.skipToken()) { Into(tp()) }
else tp()

/** FunArgType ::= Type
* | `=>' Type
* | [CaptureSet] `->' Type
Expand All @@ -1929,10 +1936,10 @@ object Parsers {
*/
def paramType(): Tree = paramTypeOf(paramValueType)

/** ParamValueType ::= Type [`*']
/** ParamValueType ::= [`into`] Type [`*']
*/
def paramValueType(): Tree = {
val t = toplevelTyp()
val t = maybeInto(toplevelTyp)
if (isIdent(nme.raw.STAR)) {
in.nextToken()
atSpan(startOffset(t)) { PostfixOp(t, Ident(tpnme.raw.STAR)) }
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ object Tokens extends TokensCommon {
final val canStartInfixTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
THIS, SUPER, USCORE, LPAREN, LBRACE, AT)

final val canStartTypeTokens: TokenSet = canStartInfixTypeTokens | BitSet(LBRACE)

final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)

final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, GIVEN)
Expand Down Expand Up @@ -287,7 +289,7 @@ object Tokens extends TokensCommon {

final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE)

final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
final val softModifierNames = Set(nme.inline, nme.into, nme.opaque, nme.open, nme.transparent, nme.infix)

def showTokenDetailed(token: Int): String = debugString(token)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case _ =>
val tsym = tycon.typeSymbol
if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*"
else if tp.isConvertibleParam then "into " ~ toText(args.head)
else if defn.isFunctionSymbol(tsym) then
toTextFunction(args, tsym.name.isContextFunction, tsym.name.isErasedFunction,
isPure = Feature.pureFunsEnabled && !tsym.name.isImpureFunction)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object TypeUtils {
case AndType(tp1, tp2) =>
// We assume that we have the following property:
// (T1, T2, ..., Tn) & (U1, U2, ..., Un) = (T1 & U1, T2 & U2, ..., Tn & Un)
tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1 & t2 }
tp1.tupleElementTypes.zip(tp2.tupleElementTypes).map { case (t1, t2) => t1.intersect(t2) }
case OrType(tp1, tp2) =>
None // We can't combine the type of two tuples
case _ =>
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -979,14 +979,15 @@ trait Checking {
sym.srcPos)

/** If `tree` is an application of a new-style implicit conversion (using the apply
* method of a `scala.Conversion` instance), check that implicit conversions are
* enabled.
* method of a `scala.Conversion` instance), check that the expected type is
* a convertible formal parameter type or that implicit conversions are enabled.
*/
def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit =
def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit =
val sym = tree.symbol
if sym.name == nme.apply
&& sym.owner.derivesFrom(defn.ConversionClass)
&& !sym.info.isErroneous
&& !expected.isConvertibleParam
then
def conv = methPart(tree) match
case Select(qual, _) => qual.symbol.orElse(sym.owner)
Expand Down Expand Up @@ -1536,7 +1537,7 @@ trait NoChecking extends ReChecking {
override def checkStable(tp: Type, pos: SrcPos, kind: String)(using Context): Unit = ()
override def checkClassType(tp: Type, pos: SrcPos, traitReq: Boolean, stablePrefixReq: Boolean)(using Context): Type = tp
override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = ()
override def checkImplicitConversionUseOK(tree: Tree)(using Context): Unit = ()
override def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = ()
override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp
override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree
override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = ()
Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1618,12 +1618,14 @@ class Namer { typer: Typer =>
def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(using Context): tpd.Tree =
typedAhead(tree, typer.typedExpr(_, pt))

def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match {
def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match
case Apply(fn, _) => typedAheadAnnotationClass(fn)
case TypeApply(fn, _) => typedAheadAnnotationClass(fn)
case Select(qual, nme.CONSTRUCTOR) => typedAheadAnnotationClass(qual)
case New(tpt) => typedAheadType(tpt).tpe.classSymbol
}
case TypedSplice(_) =>
val sym = tree.symbol
if sym.isConstructor then sym.owner else sym

/** Enter and typecheck parameter list */
def completeParams(params: List[MemberDef])(using Context): Unit = {
Expand Down