Skip to content

Commit e9a09ce

Browse files
committed
Implemented AppliedTerm, a type representing a term-level application. AppliedTerms are currently only introduced when they are stable, that is, when both the function and the arguments are. For this purpose, methods on primitive value classes are now marked stable. Also adds new syntax for singleton types -- type trees written { expr } are translated to the singleton type of expr, provided that type is stable.
1 parent d2d7d99 commit e9a09ce

13 files changed

+149
-22
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,17 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
552552
false
553553
}
554554
compareTypeBounds
555+
case tp2: AppliedTermRef =>
556+
def compareAppliedTerm = tp1 match {
557+
case _ if !ctx.phase.isTyper =>
558+
// Need to be more permissive when checking later phases, applications may have been rewritten
559+
isSubType(tp1, tp2.resType)
560+
case tp1: AppliedTermRef =>
561+
tp1.args.size == tp2.args.size && isSubType(tp1.fn, tp2.fn) &&
562+
(tp1.args zip tp2.args).forall(t => isSubType(t._1, t._2))
563+
case _ => fourthTry
564+
}
565+
compareAppliedTerm
555566
case ClassInfo(pre2, cls2, _, _, _) =>
556567
def compareClassInfo = tp1 match {
557568
case ClassInfo(pre1, cls1, _, _, _) =>
@@ -592,6 +603,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
592603
}
593604
case tp1 @ AppliedType(tycon1, args1) =>
594605
compareAppliedType1(tp1, tycon1, args1)
606+
case tp1: AppliedTermRef =>
607+
isSubType(tp1.resType, tp2)
595608
case tp1: SingletonType =>
596609
/** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/
597610
def comparePaths = tp2 match {

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

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ object Types {
111111
/** Does this type denote a stable reference (i.e. singleton type)? */
112112
final def isStable(implicit ctx: Context): Boolean = stripTypeVar match {
113113
case tp: TermRef => tp.termSymbol.isStable && tp.prefix.isStable || tp.info.isStable
114+
case tp: AppliedTermRef => tp.fn.isStable && tp.args.forall(_.isStable)
114115
case _: SingletonType | NoPrefix => true
115116
case tp: RefinedOrRecType => tp.parent.isStable
116117
case tp: ExprType => tp.resultType.isStable
@@ -286,14 +287,14 @@ object Types {
286287
/** Is this the type of a method that has a repeated parameter type as
287288
* last parameter type?
288289
*/
289-
def isVarArgsMethod(implicit ctx: Context): Boolean = stripPoly match {
290+
def isVarArgsMethod(implicit ctx: Context): Boolean = stripMethodPrefix match {
290291
case mt: MethodType => mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam
291292
case _ => false
292293
}
293294

294295
/** Is this the type of a method with a leading empty parameter list?
295296
*/
296-
def isNullaryMethod(implicit ctx: Context): Boolean = stripPoly match {
297+
def isNullaryMethod(implicit ctx: Context): Boolean = stripMethodPrefix match {
297298
case MethodType(Nil) => true
298299
case _ => false
299300
}
@@ -896,9 +897,11 @@ object Types {
896897
*/
897898
def stripAnnots(implicit ctx: Context): Type = this
898899

899-
/** Strip PolyType prefix */
900-
def stripPoly(implicit ctx: Context): Type = this match {
901-
case tp: PolyType => tp.resType.stripPoly
900+
/** Strip PolyType and AppliedTermRef prefix */
901+
// NOTE(gsps): Usages of this method always match on MethodType, so we generalize it to stripping AppliedTermRef.
902+
def stripMethodPrefix(implicit ctx: Context): Type = this match {
903+
case tp: PolyType => tp.resType.stripMethodPrefix
904+
case tp: AppliedTermRef => tp.resType.stripMethodPrefix
902905
case _ => this
903906
}
904907

@@ -1209,26 +1212,26 @@ object Types {
12091212
}
12101213

12111214
/** The parameter types of a PolyType or MethodType, Empty list for others */
1212-
final def paramInfoss(implicit ctx: Context): List[List[Type]] = stripPoly match {
1215+
final def paramInfoss(implicit ctx: Context): List[List[Type]] = stripMethodPrefix match {
12131216
case mt: MethodType => mt.paramInfos :: mt.resultType.paramInfoss
12141217
case _ => Nil
12151218
}
12161219

12171220
/** The parameter names of a PolyType or MethodType, Empty list for others */
1218-
final def paramNamess(implicit ctx: Context): List[List[TermName]] = stripPoly match {
1221+
final def paramNamess(implicit ctx: Context): List[List[TermName]] = stripMethodPrefix match {
12191222
case mt: MethodType => mt.paramNames :: mt.resultType.paramNamess
12201223
case _ => Nil
12211224
}
12221225

12231226

12241227
/** The parameter types in the first parameter section of a generic type or MethodType, Empty list for others */
1225-
final def firstParamTypes(implicit ctx: Context): List[Type] = stripPoly match {
1228+
final def firstParamTypes(implicit ctx: Context): List[Type] = stripMethodPrefix match {
12261229
case mt: MethodType => mt.paramInfos
12271230
case _ => Nil
12281231
}
12291232

12301233
/** Is this either not a method at all, or a parameterless method? */
1231-
final def isParameterless(implicit ctx: Context): Boolean = stripPoly match {
1234+
final def isParameterless(implicit ctx: Context): Boolean = stripMethodPrefix match {
12321235
case mt: MethodType => false
12331236
case _ => true
12341237
}
@@ -1239,7 +1242,7 @@ object Types {
12391242
/** The final result type of a PolyType, MethodType, or ExprType, after skipping
12401243
* all parameter sections, the type itself for all others.
12411244
*/
1242-
def finalResultType(implicit ctx: Context): Type = resultType.stripPoly match {
1245+
def finalResultType(implicit ctx: Context): Type = resultType.stripMethodPrefix match {
12431246
case mt: MethodType => mt.resultType.finalResultType
12441247
case _ => resultType
12451248
}
@@ -2123,6 +2126,55 @@ object Types {
21232126
apply(prefix, if (denot.symbol.exists) denot.symbol.asType else name).withDenot(denot)
21242127
}
21252128

2129+
// --- AppliedTermRef ---------------------------------------------------------------------
2130+
2131+
/** An opaque representation of a term-level application. **/
2132+
abstract case class AppliedTermRef(fn: /*TermRef | AppliedTermRef*/ SingletonType, args: List[Type])
2133+
extends CachedProxyType with SingletonType
2134+
{
2135+
protected[this] var myResType: Type = _
2136+
def resType(implicit ctx: Context): Type = {
2137+
if (myResType == null)
2138+
// FIXME(gsps): Sometimes TermRef(TermParamRef(<fun>, 1), +) might not have a denot, e.g., when calling
2139+
// def add[X<:Int, Y<:Int, Z<:Int](x: X, y: Y, z: {x + y}): z.type = z
2140+
fn.widen match {
2141+
case methTpe: MethodType => myResType = ctx.typer.applicationResultType(methTpe, args)
2142+
}
2143+
myResType
2144+
}
2145+
2146+
def underlying(implicit ctx: Context): Type = resType
2147+
2148+
def derivedAppliedTerm(fn: Type, args: List[Type])(implicit ctx: Context): Type =
2149+
if ((this.fn eq fn) && (this.args eq args)) this
2150+
else AppliedTermRef(fn, args)
2151+
2152+
override def computeHash = doHash(fn, args)
2153+
2154+
override def eql(that: Type) = that match {
2155+
case that: AppliedTermRef => (this.fn eq that.fn) && this.args.eqElements(that.args)
2156+
case _ => false
2157+
}
2158+
}
2159+
2160+
final class CachedAppliedTermRef(fn: SingletonType, args: List[Type]) extends AppliedTermRef(fn, args)
2161+
2162+
object AppliedTermRef {
2163+
def apply(fn: Type, args: List[Type])(implicit ctx: Context): Type = {
2164+
assertUnerased()
2165+
fn.dealias match {
2166+
case fn: TermRef => unique(new CachedAppliedTermRef(fn, args))
2167+
case fn: AppliedTermRef => unique(new CachedAppliedTermRef(fn, args))
2168+
case _ =>
2169+
fn.widenDealias match {
2170+
case methTpe: MethodType => ctx.typer.applicationResultType(methTpe, args) // TODO(gsps): Check length?
2171+
case _: WildcardType => WildcardType
2172+
case tp => throw new AssertionError(i"Don't know how to apply $tp.")
2173+
}
2174+
}
2175+
}
2176+
}
2177+
21262178
// --- Other SingletonTypes: ThisType/SuperType/ConstantType ---------------------------
21272179

21282180
/** The type cls.this
@@ -3677,7 +3729,7 @@ object Types {
36773729
object SAMType {
36783730
def zeroParamClass(tp: Type)(implicit ctx: Context): Type = tp match {
36793731
case tp: ClassInfo =>
3680-
def zeroParams(tp: Type): Boolean = tp.stripPoly match {
3732+
def zeroParams(tp: Type): Boolean = tp.stripMethodPrefix match {
36813733
case mt: MethodType => mt.paramInfos.isEmpty && !mt.resultType.isInstanceOf[MethodType]
36823734
case et: ExprType => true
36833735
case _ => false
@@ -3763,6 +3815,8 @@ object Types {
37633815
tp.derivedSuperType(thistp, supertp)
37643816
protected def derivedAppliedType(tp: AppliedType, tycon: Type, args: List[Type]): Type =
37653817
tp.derivedAppliedType(tycon, args)
3818+
protected def derivedAppliedTerm(tp: AppliedTermRef, fn: Type, args: List[Type]): Type =
3819+
tp.derivedAppliedTerm(fn, args)
37663820
protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type): Type =
37673821
tp.derivedAndOrType(tp1, tp2)
37683822
protected def derivedAnnotatedType(tp: AnnotatedType, underlying: Type, annot: Annotation): Type =
@@ -3816,6 +3870,9 @@ object Types {
38163870
}
38173871
derivedAppliedType(tp, this(tp.tycon), mapArgs(tp.args, tp.typeParams))
38183872

3873+
case tp: AppliedTermRef =>
3874+
derivedAppliedTerm(tp, this(tp.fn), tp.args.mapConserve(this))
3875+
38193876
case tp: RefinedType =>
38203877
derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo))
38213878

@@ -4109,6 +4166,25 @@ object Types {
41094166
else tp.derivedAppliedType(tycon, args)
41104167
}
41114168

4169+
override protected def derivedAppliedTerm(tp: AppliedTermRef, fn: Type, args: List[Type]): Type =
4170+
fn match {
4171+
case Range(fnLo, fnHi) =>
4172+
range(derivedAppliedTerm(tp, fnLo, args), derivedAppliedTerm(tp, fnHi, args))
4173+
case _ =>
4174+
if (fn.isBottomType) {
4175+
fn
4176+
} else if (args.exists(isRange)) {
4177+
val loBuf, hiBuf = new mutable.ListBuffer[Type]
4178+
args foreach {
4179+
case Range(lo, hi) => loBuf += lo; hiBuf += hi
4180+
case arg => loBuf += arg; hiBuf += arg
4181+
}
4182+
range(tp.derivedAppliedTerm(fn, loBuf.toList), tp.derivedAppliedTerm(fn, hiBuf.toList))
4183+
} else {
4184+
tp.derivedAppliedTerm(fn, args)
4185+
}
4186+
}
4187+
41124188
override protected def derivedAndOrType(tp: AndOrType, tp1: Type, tp2: Type) =
41134189
if (isRange(tp1) || isRange(tp2))
41144190
if (tp.isAnd) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2))
@@ -4197,6 +4273,9 @@ object Types {
41974273
}
41984274
foldArgs(this(x, tycon), tp.typeParams, args)
41994275

4276+
case tp: AppliedTermRef =>
4277+
foldOver(this(x, tp.fn), tp.args)
4278+
42004279
case _: BoundType | _: ThisType => x
42014280

42024281
case tp: LambdaType =>

compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ object TastyFormat {
400400
final val ANNOTATION = 173
401401
final val TERMREFin = 174
402402
final val TYPEREFin = 175
403+
final val APPLIEDTERM = 176
403404
final val HOLE = 255
404405

405406
final val firstSimpleTreeTag = UNITconst

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ class TreePickler(pickler: TastyPickler) {
157157
case AppliedType(tycon, args) =>
158158
writeByte(APPLIEDtype)
159159
withLength { pickleType(tycon); args.foreach(pickleType(_)) }
160+
case AppliedTermRef(fn, args) =>
161+
writeByte(APPLIEDTERM)
162+
withLength { pickleType(fn); args.foreach(pickleType(_)) }
160163
case ConstantType(value) =>
161164
pickleConstant(value)
162165
case tpe: NamedType =>

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ class TreeUnpickler(reader: TastyReader,
271271
// Eta expansion of the latter puts readType() out of the expression.
272272
case APPLIEDtype =>
273273
readType().appliedTo(until(end)(readType()))
274+
case APPLIEDTERM =>
275+
AppliedTermRef(readType(), until(end)(readType()))
274276
case TYPEBOUNDS =>
275277
TypeBounds(readType(), readType())
276278
case ANNOTATEDtype =>

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,12 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
423423
}
424424
}
425425

426+
// TODO(gsps): This is a hack to mark certain primitive methods as stable.
427+
// In the future such primitive methods should be pickled with the Stable flag set.
428+
def markPrimitiveStable(owner: Symbol, name: Name, flags: FlagSet): FlagSet =
429+
if (defn.ScalaValueClasses().contains(owner)) flags | Stable
430+
else flags
431+
426432
tag match {
427433
case NONEsym => return NoSymbol
428434
case EXTref | EXTMODCLASSref => return readExtSymbol()
@@ -447,6 +453,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
447453
name =
448454
if (name == nme.TRAIT_CONSTRUCTOR) nme.CONSTRUCTOR
449455
else name.asTermName.unmangle(Scala2MethodNameKinds)
456+
flags = markPrimitiveStable(owner, name, flags)
450457
}
451458
if ((flags is Scala2ExpandedName)) {
452459
name = name.unmangle(ExpandedName)

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ object Parsers {
858858
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true)))
859859
}
860860
else if (in.token == LBRACE)
861-
atPos(in.offset) { RefinedTypeTree(EmptyTree, refinement()) }
861+
atPos(in.offset) { inBraces(emptyRefinementOrSingletonExpr()) }
862862
else if (isSimpleLiteral) { SingletonTypeTree(literal()) }
863863
else if (in.token == USCORE) {
864864
val start = in.skipToken()
@@ -872,6 +872,11 @@ object Parsers {
872872
}
873873
}
874874

875+
def emptyRefinementOrSingletonExpr(): Tree = {
876+
if (!isStatSeqEnd && !isDclIntro) SingletonTypeTree(postfixExpr())
877+
else RefinedTypeTree(EmptyTree, refineStatSeq())
878+
}
879+
875880
val handleSingletonType: Tree => Tree = t =>
876881
if (in.token == TYPE) {
877882
in.nextToken()

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
6060
homogenize(tp.ref)
6161
case AppliedType(tycon, args) =>
6262
tycon.dealias.appliedTo(args)
63+
case tp: AppliedTermRef =>
64+
homogenize(tp.underlying)
6365
case _ =>
6466
tp
6567
}
@@ -141,6 +143,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
141143
ParamRefNameString(tp) ~ ".type"
142144
case tp: TypeParamRef =>
143145
ParamRefNameString(tp) ~ lambdaHash(tp.binder)
146+
case tp: AppliedTermRef =>
147+
toTextRef(tp) ~ ".type"
144148
case tp: SingletonType =>
145149
toTextLocal(tp.underlying) ~ "(" ~ toTextRef(tp) ~ ")"
146150
case AppliedType(tycon, args) =>
@@ -260,6 +264,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
260264
toTextPrefix(tp.prefix) ~ selectionString(tp)
261265
case tp: ThisType =>
262266
nameString(tp.cls) + ".this"
267+
case AppliedTermRef(fn, args) =>
268+
(toTextRef(fn) ~ "(" ~ Text(args map argText, ", ") ~ ")").close
263269
case SuperType(thistpe: SingletonType, _) =>
264270
toTextRef(thistpe).map(_.replaceAll("""\bthis$""", "super"))
265271
case SuperType(thistpe, _) =>

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder
394394
val apiTycon = simpleType(tycon)
395395
val apiArgs = args.map(processArg)
396396
new api.Parameterized(apiTycon, apiArgs.toArray)
397+
// TODO(gsps): Implement sbt-api translation for AppliedTermRef
398+
// case tp: AppliedTermRef =>
399+
// ???
397400
case tl: TypeLambda =>
398401
val apiTparams = tl.typeParams.map(apiTypeParameter)
399402
val apiRes = apiType(tl.resType)

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
13381338
case x => x
13391339
}
13401340

1341-
def sizeFits(alt: TermRef, tp: Type): Boolean = tp.stripPoly match {
1341+
def sizeFits(alt: TermRef, tp: Type): Boolean = tp.stripMethodPrefix match {
13421342
case tp: MethodType =>
13431343
val ptypes = tp.paramInfos
13441344
val numParams = ptypes.length

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ object Implicits {
120120
!(isFunctionInS2 || isImplicitConverter || isConforms)
121121
}
122122

123-
def discardForValueType(tpw: Type): Boolean = tpw.stripPoly match {
123+
def discardForValueType(tpw: Type): Boolean = tpw.stripMethodPrefix match {
124124
case tpw: MethodType => !tpw.isImplicitMethod
125125
case _ => false
126126
}

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,14 +359,22 @@ trait TypeAssigner {
359359
tp
360360
}
361361

362+
def applicationResultType(methTp: MethodType, args: List[Type])(implicit ctx: Context): Type =
363+
if (methTp.isDependent) safeSubstParams(methTp.resultType, methTp.paramRefs, args)
364+
else methTp.resultType
365+
362366
def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context) = {
363-
val ownType = fn.tpe.widen match {
364-
case fntpe: MethodType =>
365-
if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping)
366-
if (fntpe.isDependent) safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes)
367-
else fntpe.resultType
368-
else
369-
errorType(i"wrong number of arguments at ${ctx.phase.prev} for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.pos)
367+
val fnTpe = fn.tpe
368+
val ownType = fnTpe.widen match {
369+
case methTp: MethodType =>
370+
if (sameLength(methTp.paramInfos, args) || ctx.phase.prev.relaxedTyping) {
371+
val argTpes = args.tpes
372+
if (!ctx.erasedTypes && fnTpe.isStable && argTpes.forall(_.isStable))
373+
AppliedTermRef(fnTpe, argTpes)
374+
else
375+
applicationResultType(methTp, argTpes)
376+
} else
377+
errorType(i"wrong number of arguments at ${ctx.phase.prev} for $methTp: $fnTpe, expected: ${methTp.paramInfos.length}, found: ${args.length}", tree.pos)
370378
case t =>
371379
errorType(err.takesNoParamsStr(fn, ""), tree.pos)
372380
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ class Typer extends Namer
14391439
* @param psym Its type symbol
14401440
* @param cinfo The info of its constructor
14411441
*/
1442-
def maybeCall(ref: Tree, psym: Symbol, cinfo: Type): Tree = cinfo.stripPoly match {
1442+
def maybeCall(ref: Tree, psym: Symbol, cinfo: Type): Tree = cinfo.stripMethodPrefix match {
14431443
case cinfo @ MethodType(Nil) if cinfo.resultType.isImplicitMethod =>
14441444
val icall = New(ref).select(nme.CONSTRUCTOR).appliedToNone
14451445
typedExpr(untpd.TypedSplice(icall))(superCtx)

0 commit comments

Comments
 (0)