diff options
Diffstat (limited to 'compiler')
4 files changed, 43 insertions, 23 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 0e155b9e1..42df53fed 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -57,6 +57,7 @@ trait ConstraintHandling { b match { case b: AndOrType => occursIn(b.tp1) || occursIn(b.tp2) case b: TypeVar => occursIn(b.origin) + case b: TermRef => occursIn(b.underlying) case _ => false } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 8930983f3..6063cbf38 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -375,11 +375,22 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { thirdTryNamed(tp1, tp2) case tp2: PolyParam => def comparePolyParam = - (ctx.mode is Mode.TypevarsMissContext) || - isSubTypeWhenFrozen(tp1, bounds(tp2).lo) || { + (ctx.mode is Mode.TypevarsMissContext) || { + val alwaysTrue = + // The following condition is carefully formulated to catch all cases + // where the subtype relation is true without needing to add a constraint + // It's tricky because we might need to either appriximate tp2 by its + // lower bound or else widen tp1 and check that the result is a subtype of tp2. + // So if the constraint is not yet frozen, we do the same comparison again + // with a frozen constraint, which means that we get a chance to do the + // widening in `fourthTry` before adding to the constraint. + if (frozenConstraint || alwaysFluid) isSubType(tp1, bounds(tp2).lo) + else isSubTypeWhenFrozen(tp1, tp2) + alwaysTrue || { if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true) else fourthTry(tp1, tp2) } + } comparePolyParam case tp2: RefinedType => def compareRefinedSlow: Boolean = { diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala index 63edc0256..8b5ceb0aa 100644 --- a/compiler/src/dotty/tools/dotc/transform/Memoize.scala +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -95,28 +95,15 @@ import Decorators._ def adaptToField(tree: Tree) = if (tree.isEmpty) tree else tree.ensureConforms(field.info.widen) + val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Inline) + if (sym.is(Accessor, butNot = NoFieldNeeded)) if (sym.isGetter) { - def skipBlocks(t: Tree): Tree = t match { - case Block(_, t1) => skipBlocks(t1) - case _ => t - } - skipBlocks(tree.rhs) match { - case lit: Literal if sym.is(Final, butNot = Mutable) && isIdempotentExpr(tree.rhs) => - // duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field - // 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 - cpy.DefDef(tree)(rhs = lit) - case _ => - var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform) - if (isWildcardArg(rhs)) rhs = EmptyTree - - val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs))) - val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)) - Thicket(fieldDef, getterDef) - } + var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform) + if (isWildcardArg(rhs)) rhs = EmptyTree + val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs))) + val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info)) + Thicket(fieldDef, getterDef) } else if (sym.isSetter) { if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits @@ -127,5 +114,4 @@ import Decorators._ // neither getters nor setters else tree } - private val NoFieldNeeded = Lazy | Deferred | JavaDefined } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b70a51d1e..a053a0b0d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1173,6 +1173,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (sym.is(Inline, butNot = DeferredOrParamAccessor)) checkInlineConformant(rhs1, em"right-hand side of inline $sym") patchIfLazy(vdef1) + patchFinalVals(vdef1) vdef1 } @@ -1185,6 +1186,27 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit patch(Position(toUntyped(vdef).pos.start), "@volatile ") } + /** Adds inline to final vals with idempotent rhs + * + * duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field + * 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 + */ + private def patchFinalVals(vdef: ValDef)(implicit ctx: Context): Unit = { + def isFinalInlinableVal(sym: Symbol): Boolean = { + sym.is(Final, butNot = Mutable) && + isIdempotentExpr(vdef.rhs) /* && + ctx.scala2Mode (stay compatible with Scala2 for now) */ + } + val sym = vdef.symbol + sym.info match { + case info: ConstantType if isFinalInlinableVal(sym) && !ctx.settings.YnoInline.value => sym.setFlag(Inline) + case _ => + } + } + def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = track("typedDefDef") { val DefDef(name, tparams, vparamss, tpt, _) = ddef completeAnnotations(ddef, sym) |