From 1e5e86abbca082e055b436aba9587126780e26e8 Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Mon, 17 Feb 2020 12:09:19 +0100 Subject: [PATCH 1/8] Add AppliedTermRef type --- .../src/dotty/tools/dotc/core/Types.scala | 82 ++++++++++++++++++- .../dotty/tools/dotc/transform/Erasure.scala | 15 ++-- .../dotc/transform/FullParameterization.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 22 +++-- 4 files changed, 105 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1f23faa69fd5..3dc76133a439 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1044,9 +1044,11 @@ object Types { case _ => this } - /** Strip PolyType prefixes */ + /** Strip PolyType and AppliedTermRef prefixes */ + // TODO(gsps): Rename this to also reflect the removal of AppliedTermRefs def stripPoly(implicit ctx: Context): Type = this match { case tp: PolyType => tp.resType.stripPoly + case tp: AppliedTermRef => tp.resType.stripPoly case _ => this } @@ -2608,6 +2610,56 @@ object Types { override def hashCode: Int = System.identityHashCode(this) } + // --- AppliedTermRef ------------------------------------------------------- + + /** A precise representation of a term-level application `fn(... args)`. **/ + abstract case class AppliedTermRef(fn: /*TermRef | AppliedTermRef*/ SingletonType, args: List[Type]) + extends CachedProxyType with SingletonType + { + private[this] var myResType: Type = _ + def resType(implicit ctx: Context): Type = { + if (myResType == null) + fn.widen match { + case methTpe: MethodType => myResType = ctx.typer.applicationResultType(methTpe, args) + } + myResType + } + + def underlying(implicit ctx: Context): Type = resType + + def derivedAppliedTermRef(fn: Type, args: List[Type])(implicit ctx: Context): Type = + if ((this.fn eq fn) && (this.args eq args)) this + else AppliedTermRef(fn, args) + + override def computeHash(bs: Binders) = doHash(bs, fn, args) + override def hashIsStable: Boolean = fn.hashIsStable && args.forall(_.hashIsStable) + + override def eql(that: Type) = that match { + case that: AppliedTermRef => (this.fn eq that.fn) && this.args.eqElements(that.args) + case _ => false + } + + // TODO(gsps): AppliedTermRef#iso? + } + + final class CachedAppliedTermRef(fn: SingletonType, args: List[Type]) extends AppliedTermRef(fn, args) + + object AppliedTermRef { + def apply(fn: Type, args: List[Type])(implicit ctx: Context): Type = { + assertUnerased() + fn.dealias match { + case fn: TermRef => unique(new CachedAppliedTermRef(fn, args)) + case fn: AppliedTermRef => unique(new CachedAppliedTermRef(fn, args)) + case _ => + fn.widenDealias match { + case methTpe: MethodType => ctx.typer.applicationResultType(methTpe, args) + case _: WildcardType => WildcardType + case tp => throw new AssertionError(i"Don't know how to apply $tp.") + } + } + } + } + // --- Refined Type and RecType ------------------------------------------------ abstract class RefinedOrRecType extends CachedProxyType with ValueType { @@ -4800,6 +4852,8 @@ object Types { tp.derivedSuperType(thistp, supertp) protected def derivedAppliedType(tp: AppliedType, tycon: Type, args: List[Type]): Type = tp.derivedAppliedType(tycon, args) + protected def derivedAppliedTermRef(tp: AppliedTermRef, fn: Type, args: List[Type]): Type = + tp.derivedAppliedTermRef(fn, args) protected def derivedAndType(tp: AndType, tp1: Type, tp2: Type): Type = tp.derivedAndType(tp1, tp2) protected def derivedOrType(tp: OrType, tp1: Type, tp2: Type): Type = @@ -4859,6 +4913,9 @@ object Types { } derivedAppliedType(tp, this(tp.tycon), mapArgs(tp.args, tp.tyconTypeParams)) + case tp: AppliedTermRef => + derivedAppliedTermRef(tp, this(tp.fn), tp.args.mapConserve(this)) + case tp: RefinedType => derivedRefinedType(tp, this(tp.parent), this(tp.refinedInfo)) @@ -5173,6 +5230,26 @@ object Types { else tp.derivedAppliedType(tycon, args) } + // TODO(gsps): Double-check for changes in similar derivations + override protected def derivedAppliedTermRef(tp: AppliedTermRef, fn: Type, args: List[Type]): Type = + fn match { + case Range(fnLo, fnHi) => + range(derivedAppliedTermRef(tp, fnLo, args), derivedAppliedTermRef(tp, fnHi, args)) + case _ => + if (fn.isBottomType) { + fn + } else if (args.exists(isRange)) { + val loBuf, hiBuf = new mutable.ListBuffer[Type] + args foreach { + case Range(lo, hi) => loBuf += lo; hiBuf += hi + case arg => loBuf += arg; hiBuf += arg + } + range(tp.derivedAppliedTermRef(fn, loBuf.toList), tp.derivedAppliedTermRef(fn, hiBuf.toList)) + } else { + tp.derivedAppliedTermRef(fn, args) + } + } + override protected def derivedAndType(tp: AndType, tp1: Type, tp2: Type): Type = if (isRange(tp1) || isRange(tp2)) range(lower(tp1) & lower(tp2), upper(tp1) & upper(tp2)) else tp.derivedAndType(tp1, tp2) @@ -5275,6 +5352,9 @@ object Types { } foldArgs(this(x, tycon), tp.tyconTypeParams, args) + case tp: AppliedTermRef => + foldOver(this(x, tp.fn), tp.args) + case _: BoundType | _: ThisType => x case tp: LambdaType => diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index d325efe9e52a..ec5108d058f1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -514,13 +514,14 @@ object Erasure { ref(meth).appliedToArgs(args.toList ++ followingArgs) } - private def protoArgs(pt: Type, methTp: Type): List[untpd.Tree] = (pt, methTp) match { - case (pt: FunProto, methTp: MethodType) if methTp.isErasedMethod => - protoArgs(pt.resType, methTp.resType) - case (pt: FunProto, methTp: MethodType) => - pt.args ++ protoArgs(pt.resType, methTp.resType) - case _ => Nil - } + private def protoArgs(pt: Type, methTp: Type)(implicit ctx: Context): List[untpd.Tree] = + (pt, methTp.stripPoly) match { + case (pt: FunProto, methTp: MethodType) if methTp.isErasedMethod => + protoArgs(pt.resType, methTp.resType) + case (pt: FunProto, methTp: MethodType) => + pt.args ++ protoArgs(pt.resType, methTp.resType) + case _ => Nil + } override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(implicit ctx: Context): Tree = { val ntree = interceptTypeApply(tree.asInstanceOf[TypeApply])(ctx.withPhase(ctx.erasurePhase)).withSpan(tree.span) diff --git a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala index be4bb6d0b346..e9285c3323a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala +++ b/compiler/src/dotty/tools/dotc/transform/FullParameterization.scala @@ -231,7 +231,7 @@ trait FullParameterization { else { // this type could have changed on forwarding. Need to insert a cast. originalDef.vparamss.foldLeft(fun)((acc, vparams) => { - val meth = acc.tpe.asInstanceOf[MethodType] + val meth = acc.tpe.stripPoly.asInstanceOf[MethodType] val paramTypes = meth.instantiateParamInfos(vparams.map(_.tpe)) acc.appliedToArgs( vparams.lazyZip(paramTypes).map((vparam, paramType) => { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index f6d4259fdb6b..eef95022d2f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -416,14 +416,22 @@ trait TypeAssigner { tp } + def applicationResultType(methTp: MethodType, args: List[Type])(implicit ctx: Context): Type = + if (methTp.isResultDependent) safeSubstParams(methTp.resultType, methTp.paramRefs, args) + else methTp.resultType + def assignType(tree: untpd.Apply, fn: Tree, args: List[Tree])(implicit ctx: Context): Apply = { - val ownType = fn.tpe.widen match { - case fntpe: MethodType => - if (sameLength(fntpe.paramInfos, args) || ctx.phase.prev.relaxedTyping) - if (fntpe.isResultDependent) safeSubstParams(fntpe.resultType, fntpe.paramRefs, args.tpes) - else fntpe.resultType - else - errorType(i"wrong number of arguments at ${ctx.phase.prev} for $fntpe: ${fn.tpe}, expected: ${fntpe.paramInfos.length}, found: ${args.length}", tree.sourcePos) + val fnTpe = fn.tpe + val ownType = fnTpe.widen match { + case methTp: MethodType => + if (sameLength(methTp.paramInfos, args) || ctx.phase.prev.relaxedTyping) { + val argTpes = args.tpes + if (!ctx.erasedTypes && fnTpe.isStable && argTpes.forall(_.isStable)) + AppliedTermRef(fnTpe, argTpes) + else + applicationResultType(methTp, argTpes) + } else + errorType(i"wrong number of arguments at ${ctx.phase.prev} for $methTp: $fnTpe, expected: ${methTp.paramInfos.length}, found: ${args.length}", tree.sourcePos) case t => if (ctx.settings.Ydebug.value) new FatalError("").printStackTrace() errorType(err.takesNoParamsStr(fn, ""), tree.sourcePos) From 6dcc75d5295f5092eb932b65fee39acaff632725 Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Mon, 17 Feb 2020 12:17:18 +0100 Subject: [PATCH 2/8] Add curly-brace singleton type syntax for AppliedTermRef --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 8 +++++++- docs/docs/internals/syntax.md | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index afc4377e8d25..c3d39be9b876 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1536,6 +1536,7 @@ object Parsers { * | `(' ArgTypes `)' * | `_' TypeBounds * | Refinement + * | `{` PostfixExpr `}` * | Literal * | ‘$’ ‘{’ Block ‘}’ */ @@ -1545,7 +1546,7 @@ object Parsers { makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true))) } else if (in.token == LBRACE) - atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement()) } + atSpan(in.offset) { inBraces(emptyRefinementOrSingletonExpr()) } else if (isSimpleLiteral) { SingletonTypeTree(literal(inType = true)) } else if (isIdent(nme.raw.MINUS) && in.lookaheadIn(numericLitTokens)) { val start = in.offset @@ -1575,6 +1576,11 @@ object Parsers { } } + def emptyRefinementOrSingletonExpr(): Tree = { + if (!isStatSeqEnd && !isDclIntro) SingletonTypeTree(postfixExpr()) + else RefinedTypeTree(EmptyTree, refineStatSeq()) + } + val handleSingletonType: Tree => Tree = t => if (in.token == TYPE) { in.nextToken() diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 6a1a169eece9..de9f89371091 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -160,6 +160,7 @@ SimpleType ::= SimpleType TypeArgs | ‘(’ ArgTypes ‘)’ Tuple(ts) | ‘?’ SubtypeBounds | Refinement RefinedTypeTree(EmptyTree, refinement) + | `{` PostfixExpr `}` SingletonTypeTree(expr) | SimpleLiteral SingletonTypeTree(l) | ‘$’ ‘{’ Block ‘}’ ArgTypes ::= Type {‘,’ Type} From 1159370657b9f13d668886ea0e3289c4b444ab08 Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Mon, 17 Feb 2020 14:14:20 +0100 Subject: [PATCH 3/8] Subtyping, Stable primitives & cleaned-up creation of AppliedTermRef --- .../dotty/tools/dotc/core/TypeComparer.scala | 9 +++++ .../src/dotty/tools/dotc/core/Types.scala | 35 ++++++++++++++----- .../core/unpickleScala2/Scala2Unpickler.scala | 9 ++++- .../dotty/tools/dotc/typer/TypeAssigner.scala | 10 ++---- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e6fbd1019c84..14179e6c52c0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -699,6 +699,15 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w false } compareTypeBounds + case tp2: AppliedTermRef => + // TODO(gsps): Check whether rule or position thereof should change + def compareAppliedTerm = tp1 match { + case tp1: AppliedTermRef => + sameLength(tp1.args, tp2.args) && isSubType(tp1.fn, tp2.fn) && + tp1.args.zip(tp2.args).forall((arg1, arg2) => isSubType(arg1, arg2)) + case _ => fourthTry + } + compareAppliedTerm case tp2: AnnotatedType if tp2.isRefining => (tp1.derivesAnnotWith(tp2.annot.sameAnnotation) || defn.isBottomType(tp1)) && recur(tp1, tp2.parent) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3dc76133a439..d698f27518bf 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2627,9 +2627,12 @@ object Types { def underlying(implicit ctx: Context): Type = resType + /** Compute the derived AppliedTermRef, widening to the result type if any of its + * components is unstable. + */ def derivedAppliedTermRef(fn: Type, args: List[Type])(implicit ctx: Context): Type = if ((this.fn eq fn) && (this.args eq args)) this - else AppliedTermRef(fn, args) + else AppliedTermRef.make(fn, args) override def computeHash(bs: Binders) = doHash(bs, fn, args) override def hashIsStable: Boolean = fn.hashIsStable && args.forall(_.hashIsStable) @@ -2645,17 +2648,31 @@ object Types { final class CachedAppliedTermRef(fn: SingletonType, args: List[Type]) extends AppliedTermRef(fn, args) object AppliedTermRef { - def apply(fn: Type, args: List[Type])(implicit ctx: Context): Type = { + def apply(fn: SingletonType, args: List[Type])(implicit ctx: Context): AppliedTermRef = { assertUnerased() + assert(fn.isStable, args.forall(_.isStable)) + unique(new CachedAppliedTermRef(fn, args)) + } + + def make(fn: Type, args: List[Type])(implicit ctx: Context): Type = { + def fallbackToResult(): Type = + fn.widenDealias match { + case methTpe: MethodType => ctx.typer.applicationResultType(methTpe, args) + case _: WildcardType => WildcardType + case tp => throw new AssertionError(i"Don't know how to apply $tp.") + } + def complete(fn: SingletonType): Type = + if (!ctx.erasedTypes && fn.isStable && args.forall(_.isStable)) + AppliedTermRef(fn, args) + else + fallbackToResult() fn.dealias match { - case fn: TermRef => unique(new CachedAppliedTermRef(fn, args)) - case fn: AppliedTermRef => unique(new CachedAppliedTermRef(fn, args)) + case fn: TermRef => + complete(fn) + case fn: AppliedTermRef => + complete(fn) case _ => - fn.widenDealias match { - case methTpe: MethodType => ctx.typer.applicationResultType(methTpe, args) - case _: WildcardType => WildcardType - case tp => throw new AssertionError(i"Don't know how to apply $tp.") - } + fallbackToResult() } } } diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 10def4aa7693..64b09e2618e5 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -437,6 +437,11 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } } + // TODO(gsps): This is a hack to mark certain primitive methods as stable. + // In the future such primitive methods should be pickled with the StableRealizable flag set. + def markPrimitiveStable(owner: Symbol, name: Name, flags: FlagSet): FlagSet = + if (tpnme.ScalaValueNames.contains(defn.scalaClassName(owner))) flags | StableRealizable else flags + tag match { case NONEsym => return NoSymbol case EXTref | EXTMODCLASSref => return readExtSymbol() @@ -457,10 +462,12 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } name = name.adjustIfModuleClass(flags) - if (flags.is(Method)) + if (flags.is(Method)) { name = if (name == nme.TRAIT_CONSTRUCTOR) nme.CONSTRUCTOR else name.asTermName.unmangle(Scala2MethodNameKinds) + flags = markPrimitiveStable(owner, name, flags) + } if ((flags.is(Scala2ExpandedName))) { name = name.unmangle(ExpandedName) flags = flags &~ Scala2ExpandedName diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index eef95022d2f5..5c7427cf57e0 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -424,13 +424,9 @@ trait TypeAssigner { val fnTpe = fn.tpe val ownType = fnTpe.widen match { case methTp: MethodType => - if (sameLength(methTp.paramInfos, args) || ctx.phase.prev.relaxedTyping) { - val argTpes = args.tpes - if (!ctx.erasedTypes && fnTpe.isStable && argTpes.forall(_.isStable)) - AppliedTermRef(fnTpe, argTpes) - else - applicationResultType(methTp, argTpes) - } else + if (sameLength(methTp.paramInfos, args) || ctx.phase.prev.relaxedTyping) + AppliedTermRef.make(fnTpe, args.tpes) + else errorType(i"wrong number of arguments at ${ctx.phase.prev} for $methTp: $fnTpe, expected: ${methTp.paramInfos.length}, found: ${args.length}", tree.sourcePos) case t => if (ctx.settings.Ydebug.value) new FatalError("").printStackTrace() From 058c67300447eca1590e2f12ce83fcc19c93fbb3 Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Mon, 17 Feb 2020 15:19:41 +0100 Subject: [PATCH 4/8] Add AppliedTermRef pickling and (preliminary) pretty printing --- compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala | 3 +++ compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala | 3 +++ compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 ++ compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 5 +++++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 5 ++++- 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 3dea03543393..9fc2c0a05c58 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -91,6 +91,9 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { POLYtype | TYPELAMBDAtype => printTree() until(end) { printName(); printTree() } + case APPLIEDTERMREF => + printTree() + until(end) { printTree() } case PARAMtype => printNat(); printNat() case _ => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 0afaab9d757f..6b0af42e8f11 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -167,6 +167,9 @@ class TreePickler(pickler: TastyPickler) { case AppliedType(tycon, args) => writeByte(APPLIEDtype) withLength { pickleType(tycon); args.foreach(pickleType(_)) } + case AppliedTermRef(fn, args) => + writeByte(APPLIEDTERMREF) + withLength { pickleType(fn); args.foreach(pickleType(_)) } case ConstantType(value) => pickleConstant(value) case tpe: NamedType => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 96a8cfe5e8a2..08a263146efd 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -344,6 +344,8 @@ class TreeUnpickler(reader: TastyReader, // Eta expansion of the latter puts readType() out of the expression. case APPLIEDtype => readType().appliedTo(until(end)(readType())) + case APPLIEDTERMREF => + AppliedTermRef(readType().asInstanceOf[SingletonType], until(end)(readType())) case TYPEBOUNDS => val lo = readType() if nothingButMods(end) then diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 0a88c0fa6fe7..ae1f10532f08 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -64,6 +64,8 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp @ AppliedType(tycon, args) => if (defn.isCompiletimeAppliedType(tycon.typeSymbol)) tp.tryCompiletimeConstantFold else tycon.dealias.appliedTo(args) + case tp @ AppliedTermRef(fn, args) => + tp.derivedAppliedTermRef(homogenize(tp), args.mapConserve(homogenize)) case _ => tp } @@ -288,6 +290,9 @@ class PlainPrinter(_ctx: Context) extends Printer { tp match { case tp: TermRef => toTextPrefix(tp.prefix) ~ selectionString(tp) + case AppliedTermRef(fn, args) => + // TODO(gsps): Print AppliedTermRef as in surface syntax (using curly braces) + (toTextRef(fn) ~ "(" ~ Text(args map argText, ", ") ~ ")").close case tp: ThisType => nameString(tp.cls) + ".this" case SuperType(thistpe: SingletonType, _) => diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 439be1400cd0..e308b3762c5c 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -126,6 +126,7 @@ Standard-Section: "ASTs" TopLevelStat* THIS clsRef_Type -- cls.this RECthis recType_ASTRef -- The `this` in a recursive refined type `recType`. SHAREDtype path_ASTRef -- link to previously serialized path + APPLIEDTERMREF Length fn_Type arg_Type* -- The stable result of `fn` applied to `arg`s Constant = UNITconst -- () FALSEconst -- false @@ -254,7 +255,7 @@ object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) val MajorVersion: Int = 20 - val MinorVersion: Int = 0 + val MinorVersion: Int = 1 /** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */ class NameTags { @@ -453,6 +454,7 @@ object TastyFormat { final val ANNOTATION = 173 final val TERMREFin = 174 final val TYPEREFin = 175 + final val APPLIEDTERMREF = 176 final val METHODtype = 180 final val ERASEDMETHODtype = 181 @@ -660,6 +662,7 @@ object TastyFormat { case SUPERtype => "SUPERtype" case TERMREFin => "TERMREFin" case TYPEREFin => "TYPEREFin" + case APPLIEDTERMREF => "APPLIEDTERMREF" case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" From 10e182b6b31608785f871642d6d258ef5aaf56e4 Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Mon, 17 Feb 2020 16:49:25 +0100 Subject: [PATCH 5/8] Fix AppliedTermRef pretty printing --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index ae1f10532f08..a366a8e812d0 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -142,6 +142,8 @@ class PlainPrinter(_ctx: Context) extends Printer { toTextRef(tp) ~ ".type" case tp: TermRef if tp.denot.isOverloaded => "" + case tp: AppliedTermRef => + "{" ~ toTextRef(tp) ~ "}" case tp: TypeRef => if (printWithoutPrefix.contains(tp.symbol)) toText(tp.name) @@ -291,8 +293,11 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: TermRef => toTextPrefix(tp.prefix) ~ selectionString(tp) case AppliedTermRef(fn, args) => - // TODO(gsps): Print AppliedTermRef as in surface syntax (using curly braces) - (toTextRef(fn) ~ "(" ~ Text(args map argText, ", ") ~ ")").close + val argTexts = args.map { + case arg: SingletonType => toTextRef(arg) + case arg => argText(arg) + } + (toTextRef(fn) ~ "(" ~ Text(argTexts, ", ") ~ ")").close case tp: ThisType => nameString(tp.cls) + ".this" case SuperType(thistpe: SingletonType, _) => From aa832f89a49e606b20479b7339d019f252a2003a Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Mon, 17 Feb 2020 17:21:41 +0100 Subject: [PATCH 6/8] Add existing tests and a new one inspired by recent issue --- tests/neg/appliedTerm.scala | 25 +++++++++ tests/pos/appliedTerm.scala | 100 ++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 tests/neg/appliedTerm.scala create mode 100644 tests/pos/appliedTerm.scala diff --git a/tests/neg/appliedTerm.scala b/tests/neg/appliedTerm.scala new file mode 100644 index 000000000000..34c928ea928f --- /dev/null +++ b/tests/neg/appliedTerm.scala @@ -0,0 +1,25 @@ +object SimpleEqs { + val x = 1 + val y: {x} = x + implicitly[{x + 1} =:= {y}] // error + implicitly[{x + 1} =:= {y + 2}] // error + implicitly[{x + 1} =:= {1 + y}] // error: TypeComparer doesn't know about commutativity + + val b = true + implicitly[{b} =:= {b}] + implicitly[{!b} =:= {!b}] + implicitly[{!b} =:= {b}] // error +} + + +object Stability { + def f1(x: Int): Int = x + def f2(x: Int): {x} = x + + val x = 1 + implicitly[{f1(x)} =:= {x}] // error: f1 is not considered stable // error: f1's result type is not precise enough + implicitly[{f1(x)} =:= {f1(x)}] // error: f1 is not considered stable // error: f1 is not considered stable + implicitly[{f2(x)} =:= {x}] + implicitly[{f2(x)} =:= {f2(x)}] + implicitly[{f1(x)} =:= {f2(x)}] // error: f1 is not considered stable // error: f1's result type is not precise enough +} diff --git a/tests/pos/appliedTerm.scala b/tests/pos/appliedTerm.scala new file mode 100644 index 000000000000..414e06eec2ae --- /dev/null +++ b/tests/pos/appliedTerm.scala @@ -0,0 +1,100 @@ +object SimpleEqs { + val x = 1 + val y: {x} = x + + type YPlusOne = {y + 1} + + implicitly[{x + 1} =:= {y + 1}] + implicitly[{x + 1} =:= YPlusOne] +} + + +object AvoidLocalRefs { + type Id[T] = T + + val x = 1 + def y = { val a: {x} = x; val t: Id[{a + 1}] = a + 1; t } + def z: {x + 1} = { val a: {x} = x; val t: Id[{a + 1}] = a + 1; t } + + val _ = { val a = 0; a + 1 } + val _ = { val a = 0; 1 + a } +} + + +object Bounds { + @annotation.implicitNotFound(msg = "Cannot prove that ${B} holds.") + sealed abstract class P[B <: Boolean](val b: B) + private[this] val prop_singleton = new P[true](true) {} + object P { + def assume(b: Boolean): P[b.type] = prop_singleton.asInstanceOf[P[b.type]] + } + + def if_(cond: Boolean): (P[cond.type] ?=> Unit) => Unit = + thn => if (cond) thn(using P.assume(cond)) + + + // Bounds-checked + + def index(k: Int)(implicit ev: P[{k >= 0}]): Int = k + + def run(i: Int) = + if_(i >= 0) { + index(i) + } + + + // Boxed value with a predicate + + class PredBox[T, B <: Boolean](val v: T)(val p: P[B]) + object PredBox { + def apply[T, B <: Boolean](v: T)(implicit ev: P[B]) = new PredBox[T, B](v)(ev) + } + + def run2(i: Int) = + if_(i != 0) { + PredBox[Int, {i != 0}](i) + } +} + + +object ArithmeticIdentities { + type SInt = Int & Singleton + + class DecomposeHelper[V <: SInt](val v: V) { + import DecomposeHelper._ + def asSumOf[X <: SInt, Y <: SInt](x: X, y: Y)(implicit ev: {v} =:= {x + y}): SumOf[{x}, {y}] = SumOf(x, y)(ev(v)) + } + + object DecomposeHelper { + /* Axioms */ + sealed trait Decomposition[V <: SInt] + case class SumOf[X <: SInt, Y <: SInt](x: X, y: Y)(val v: {x + y}) extends Decomposition[{v}] { + def commuted: SumOf[Y, X] = SumOf(y, x)(v.asInstanceOf[{y + x}]) + } + } + + implicit def toDecomposeHelper[V <: Int](v: V): DecomposeHelper[v.type] = new DecomposeHelper(v) + + + // Let's "show" that x + 1 == 1 + x + + val x = 123 + (x + 1).asSumOf(x, 1).v: {x + 1} + (x + 1).asSumOf(x, 1).commuted.v: {1 + x} +} + + +object Matrices { + type SInt = Int & Singleton + + case class Matrix[M <: SInt, N <: SInt](m: M, n: N) { + val size: {m * n} = m * n + } + + val a: 123 = 123 + val b: Int = ??? + val mat = Matrix(a, b) + val _ = mat.m: 123 + val _ = mat.n: b.type + val _ = mat.size: {123 * b} +} From 0261183d0c8a6c0ca84063ca042c3d2c7fc98252 Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Tue, 18 Feb 2020 10:55:54 +0100 Subject: [PATCH 7/8] Fix homogenize and add TODO for summoning --- compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index a366a8e812d0..1c3525694848 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -65,7 +65,7 @@ class PlainPrinter(_ctx: Context) extends Printer { if (defn.isCompiletimeAppliedType(tycon.typeSymbol)) tp.tryCompiletimeConstantFold else tycon.dealias.appliedTo(args) case tp @ AppliedTermRef(fn, args) => - tp.derivedAppliedTermRef(homogenize(tp), args.mapConserve(homogenize)) + tp.derivedAppliedTermRef(homogenize(fn), args.mapConserve(homogenize)) case _ => tp } diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4a5859363440..5a2b4a930e8b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -869,6 +869,7 @@ trait Implicits { self: Typer => success(Literal(Constant(()))) case n: TermRef => success(ref(n)) + // TODO(gsps): Handle AppliedTermRef case tp => EmptyTree } From aa67ec8a8ab7e7b20c1d673efa14ac383b037f0d Mon Sep 17 00:00:00 2001 From: Georg Schmid Date: Tue, 18 Feb 2020 11:13:16 +0100 Subject: [PATCH 8/8] Adapt check-init test to not fail on unrelated pure expression warning --- tests/init/neg/private.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/init/neg/private.scala b/tests/init/neg/private.scala index 658860ab577c..f1fee5cc4e30 100644 --- a/tests/init/neg/private.scala +++ b/tests/init/neg/private.scala @@ -1,5 +1,5 @@ class A(a: Int) { - a + 3 + val _ = a + 3 def foo() = a * 2 }