diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index d1e91799f31a..bd9195f77ab3 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -170,14 +170,15 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => case _ => false } - /** Is this argument node of the form : _* ? + /** Is this argument node of the form : _*, or is it a reference to + * such an argument ? The latter case can happen when an argument is lifted. */ def isWildcardStarArg(tree: Tree)(implicit ctx: Context): Boolean = unbind(tree) match { case Typed(Ident(nme.WILDCARD_STAR), _) => true case Typed(_, Ident(tpnme.WILDCARD_STAR)) => true - case Typed(_, tpt: TypeTree) => tpt.hasType && tpt.tpe.isRepeatedParam + case Typed(_, tpt: TypeTree) => tpt.typeOpt.isRepeatedParam case NamedArg(_, arg) => isWildcardStarArg(arg) - case _ => false + case arg => arg.typeOpt.widen.isRepeatedParam } /** If this tree has type parameters, those. Otherwise Nil. @@ -317,7 +318,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * Idempotent if running the statement a second time has no side effects * Impure otherwise */ - private def statPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { + def statPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { case EmptyTree | TypeDef(_, _) | Import(_, _) @@ -342,7 +343,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * takes a different code path than all to follow; but they are idempotent * because running the expression a second time gives the cached result. */ - private def exprPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { + def exprPurity(tree: Tree)(implicit ctx: Context): PurityLevel = unsplice(tree) match { case EmptyTree | This(_) | Super(_, _) @@ -397,7 +398,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * @DarkDimius: need to make sure that lazy accessor methods have Lazy and Stable * flags set. */ - private def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = + def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = if (!tree.tpe.widen.isParameterless || tree.symbol.is(Erased)) SimplyPure else if (!tree.symbol.isStable) Impure else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start. diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0e6d98f2e3e4..6500693da068 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -218,6 +218,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { return "FunProto(" ~ argsText ~ "):" ~ toText(resultType) case tp: IgnoredProto => return "?" + case tp @ PolyProto(targs, resType) => + return "PolyProto(" ~ toTextGlobal(targs, ", ") ~ "): " ~ toText(resType) case _ => } super.toText(tp) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index e6886b99e431..8f700481f111 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -80,7 +80,8 @@ class TreeChecker extends Phase with SymTransformer { if (sym.isClass && !sym.isAbsent) { val validSuperclass = sym.isPrimitiveValueClass || defn.syntheticCoreClasses.contains(sym) || - (sym eq defn.ObjectClass) || (sym is NoSuperClass) || (sym.asClass.superClass.exists) + (sym eq defn.ObjectClass) || (sym is NoSuperClass) || (sym.asClass.superClass.exists) || + sym.isRefinementClass assert(validSuperclass, i"$sym has no superclass set") testDuplicate(sym, seenClasses, "class") @@ -97,7 +98,10 @@ class TreeChecker extends Phase with SymTransformer { def phaseName: String = "Ycheck" def run(implicit ctx: Context): Unit = { - check(ctx.allPhases, ctx) + if (ctx.settings.YtestPickler.value && ctx.phase.prev.isInstanceOf[Pickler]) + ctx.echo("Skipping Ycheck after pickling with -Ytest-pickler, the returned tree contains stale symbols") + else + check(ctx.allPhases, ctx) } private def previousPhases(phases: List[Phase])(implicit ctx: Context): List[Phase] = phases match { @@ -305,7 +309,7 @@ class TreeChecker extends Phase with SymTransformer { override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = { assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase) - assert(tree.isType || !needsSelect(tree.tpe), i"bad type ${tree.tpe} for $tree # ${tree.uniqueId}") + assert(tree.isType || ctx.mode.is(Mode.Pattern) && untpd.isWildcardArg(tree) || !needsSelect(tree.tpe), i"bad type ${tree.tpe} for $tree # ${tree.uniqueId}") assertDefined(tree) checkNotRepeated(super.typedIdent(tree, pt)) @@ -443,7 +447,8 @@ class TreeChecker extends Phase with SymTransformer { if (ctx.mode.isExpr && !tree.isEmpty && !isPrimaryConstructorReturn && - !pt.isInstanceOf[FunProto]) + !pt.isInstanceOf[FunProto] && + !pt.isInstanceOf[PolyProto]) assert(tree.tpe <:< pt, { val mismatch = err.typeMismatchMsg(tree.tpe, pt) i"""|${mismatch.msg} diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 698fdaff0c15..742d5c41942e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -13,6 +13,7 @@ import StdNames._ import NameOps._ import Symbols._ import Trees._ +import TreeInfo._ import ProtoTypes._ import Constants._ import Scopes._ @@ -601,17 +602,20 @@ trait Checking { } } - /** Check that `tree` is a pure expression of constant type */ - def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context): Unit = + /** Check that `tree` can be marked `inline` */ + def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = { + // final vals can be marked inline even if they're not pure, see Typer#patchFinalVals + val purityLevel = if (isFinal) Idempotent else Pure tree.tpe match { case tp: TermRef if tp.symbol.is(InlineParam) => // ok case tp => tp.widenTermRefExpr match { - case tp: ConstantType if isPureExpr(tree) => // ok - case tp if defn.isFunctionType(tp) && isPureExpr(tree) => // ok + case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok + case tp if defn.isFunctionType(tp) && exprPurity(tree) >= purityLevel => // ok case _ => if (!ctx.erasedTypes) ctx.error(em"$what must be a constant expression or a function", tree.pos) } } + } /** Check that class does not declare same symbol twice */ def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = { @@ -867,7 +871,7 @@ trait NoChecking extends ReChecking { override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () override def checkFeasibleParent(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp - override def checkInlineConformant(tree: Tree, what: => String)(implicit ctx: Context) = () + override def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context) = () override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 431721dc44e6..fcba55cf99d4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1371,7 +1371,7 @@ class Typer extends Namer } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) - checkInlineConformant(rhs1, em"right-hand side of inline $sym") + checkInlineConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of inline $sym") patchIfLazy(vdef1) patchFinalVals(vdef1) vdef1 @@ -1392,7 +1392,7 @@ class Typer extends Namer * and instead return the value. This seemingly minor optimization has huge effect on initialization * order and the values that can be observed during superconstructor call * - * see remark about idempotency in PostTyper#normalizeTree + * see remark about idempotency in TreeInfo#constToLiteral */ private def patchFinalVals(vdef: ValDef)(implicit ctx: Context): Unit = { def isFinalInlinableVal(sym: Symbol): Boolean = { @@ -2329,7 +2329,7 @@ class Typer extends Namer } else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) - checkInlineConformant(tree, "argument to inline parameter") + checkInlineConformant(tree, isFinal = false, "argument to inline parameter") if (Inliner.hasBodyToInline(tree.symbol) && !ctx.owner.ownersIterator.exists(_.isInlineMethod) && !ctx.settings.YnoInline.value && diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 845908be2e0f..486d968a24bb 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -45,7 +45,7 @@ object TestConfiguration { } // Ideally should be Ycheck:all - val yCheckOptions = Array("-Ycheck:elimJavaPackages,refchecks,splitter,arrayConstructors,erasure,capturedVars,getClass,elimStaticThis,labelDef") + val yCheckOptions = Array("-Ycheck:all") val basicDefaultOptions = checkOptions ++ noCheckOptions ++ yCheckOptions val defaultUnoptimised = TestFlags(classPath, runClassPath, basicDefaultOptions) diff --git a/tests/pos/i3971.scala b/tests/pos/i3971.scala new file mode 100644 index 000000000000..d89d9c1b36f9 --- /dev/null +++ b/tests/pos/i3971.scala @@ -0,0 +1,3 @@ +object BadInlineConstCheck { + final val MaxSize = Int.MaxValue + 0 +}