From e204e30bd68ac109ab96254f319ee0797dd01373 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 18 Oct 2023 14:48:32 +0200 Subject: [PATCH] Don't emit line number for synthetic unit value Synthetic `()` values are added to blocks without an expression. Don't emit a line number for them. Implemented by checking the `SyntheticUnit` attachment. This seems simpler than trying to control the position assigned to synthetic unit trees, as they are created in many places. --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 45 +++++++++---------- .../src/dotty/tools/dotc/ast/Desugar.scala | 4 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 + compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../dotty/tools/dotc/inlines/Inliner.scala | 2 +- .../dotty/tools/dotc/inlines/Inlines.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 8 ++-- .../tools/dotc/transform/DropBreaks.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 2 +- .../dotty/tools/dotc/transform/Memoize.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 2 +- .../tools/dotc/transform/TypeTestsCasts.scala | 2 +- .../dotty/tools/dotc/typer/RefChecks.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../backend/jvm/DottyBytecodeTests.scala | 19 ++++++++ 16 files changed, 60 insertions(+), 40 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 70d4d758d83b..073cc44e76b7 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -3,26 +3,24 @@ package backend package jvm import scala.language.unsafeNulls - import scala.annotation.tailrec - -import scala.collection.{ mutable, immutable } - +import scala.collection.{immutable, mutable} import scala.tools.asm import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.TreeTypeMap import dotty.tools.dotc.CompilationUnit -import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.StdNames._ -import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.ast.Trees.SyntheticUnit +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Flags.* +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.NameKinds.* import dotty.tools.dotc.core.Names.TermName -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.util.Spans._ +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.util.Spans.* import dotty.tools.dotc.report -import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.transform.SymUtils.* /* * @@ -624,16 +622,17 @@ trait BCodeSkelBuilder extends BCodeHelpers { case _ => a } - if (!emitLines || !tree.span.exists) return; - val nr = ctx.source.offsetToLine(tree.span.point) + 1 - if (nr != lastEmittedLineNr) { - lastEmittedLineNr = nr - getNonLabelNode(lastInsn) match { - case lnn: asm.tree.LineNumberNode => - // overwrite previous landmark as no instructions have been emitted for it - lnn.line = nr - case _ => - mnode.visitLineNumber(nr, currProgramPoint()) + if (emitLines && tree.span.exists && !tree.hasAttachment(SyntheticUnit)) { + val nr = ctx.source.offsetToLine(tree.span.point) + 1 + if (nr != lastEmittedLineNr) { + lastEmittedLineNr = nr + getNonLabelNode(lastInsn) match { + case lnn: asm.tree.LineNumberNode => + // overwrite previous landmark as no instructions have been emitted for it + lnn.line = nr + case _ => + mnode.visitLineNumber(nr, currProgramPoint()) + } } } } diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 6024eab29722..552c979d8d3c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1844,7 +1844,7 @@ object desugar { case ts: Thicket => ts.trees.tail case t => Nil } map { - case Block(Nil, EmptyTree) => Literal(Constant(())) // for s"... ${} ..." + case Block(Nil, EmptyTree) => unitLiteral // for s"... ${} ..." case Block(Nil, expr) => expr // important for interpolated string as patterns, see i1773.scala case t => t } @@ -1872,7 +1872,7 @@ object desugar { val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt)) flatTree(pats1 map (makePatDef(tree, mods, _, rhs))) case ext: ExtMethods => - Block(List(ext), Literal(Constant(())).withSpan(ext.span)) + Block(List(ext), unitLiteral.withSpan(ext.span)) case f: FunctionWithMods if f.hasErasedParams => makeFunctionWithValDefs(f, pt) } desugared.withSpan(tree.span) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 9678571e0106..6941596e1c9b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -31,6 +31,8 @@ object Trees { /** Property key for backquoted identifiers and definitions */ val Backquoted: Property.StickyKey[Unit] = Property.StickyKey() + + val SyntheticUnit: Property.StickyKey[Unit] = Property.StickyKey() /** Trees take a parameter indicating what the type of their `tpe` field * is. Two choices: `Type` or `Untyped`. diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 973af0e8781d..bf954dae459c 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -64,7 +64,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { ta.assignType(untpd.Literal(const)) def unitLiteral(using Context): Literal = - Literal(Constant(())) + Literal(Constant(())).withAttachment(SyntheticUnit, ()) def nullLiteral(using Context): Literal = Literal(Constant(null)) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e7d38da854a4..41ba452fa80a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -492,7 +492,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def InferredTypeTree(tpe: Type)(using Context): TypedSplice = TypedSplice(new InferredTypeTree().withTypeUnchecked(tpe)) - def unitLiteral(implicit src: SourceFile): Literal = Literal(Constant(())) + def unitLiteral(implicit src: SourceFile): Literal = Literal(Constant(())).withAttachment(SyntheticUnit, ()) def ref(tp: NamedType)(using Context): Tree = TypedSplice(tpd.ref(tp)) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index f038dbbeed31..b7bfdcf80e24 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -793,7 +793,7 @@ class Inliner(val call: tpd.Tree)(using Context): typed(tree.cond, defn.BooleanType)(using condCtx) match { case cond1 @ ConstantValue(b: Boolean) => val selected0 = if (b) tree.thenp else tree.elsep - val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) + val selected = if (selected0.isEmpty) tpd.unitLiteral else typed(selected0, pt) if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) case cond1 => diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 030db206e052..25e9b1480370 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -408,7 +408,7 @@ object Inlines: arg match case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) - return Literal(Constant(())).withSpan(call.span) + return unitLiteral.withSpan(call.span) else if inlinedMethod == defn.Compiletime_codeOf then return Intrinsics.codeOf(arg, call.srcPos) case _ => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 05e5c34b5a0f..363c95f6c357 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2211,7 +2211,7 @@ object Parsers { case _ => } patch(source, cond.span.endPos, "}) ()") - WhileDo(Block(body, cond), Literal(Constant(()))) + WhileDo(Block(body, cond), unitLiteral) } case TRY => val tryOffset = in.offset @@ -2241,7 +2241,7 @@ object Parsers { in.nextToken(); val expr = subExpr() if expr.span.exists then expr - else Literal(Constant(())) // finally without an expression + else unitLiteral // finally without an expression } else { if handler.isEmpty then @@ -3728,10 +3728,10 @@ object Parsers { val stats = selfInvocation() :: ( if (isStatSep) { in.nextToken(); blockStatSeq() } else Nil) - Block(stats, Literal(Constant(()))) + Block(stats, unitLiteral) } } - else Block(selfInvocation() :: Nil, Literal(Constant(()))) + else Block(selfInvocation() :: Nil, unitLiteral) /** SelfInvocation ::= this ArgumentExprs {ArgumentExprs} */ diff --git a/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala b/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala index 3081bd5c2b20..9f94a8c13a52 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala +++ b/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala @@ -122,7 +122,7 @@ class DropBreaks extends MiniPhase: case id: Ident => val arg = (args: @unchecked) match case arg :: Nil => arg - case Nil => Literal(Constant(())).withSpan(tree.span) + case Nil => unitLiteral.withSpan(tree.span) Some((id.symbol, arg)) case _ => None case _ => None diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 7c77d7809bdf..0b52b1725c3e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -319,7 +319,7 @@ object Erasure { cast(tree1, pt) case _ => val cls = pt.classSymbol - if (cls eq defn.UnitClass) constant(tree, Literal(Constant(()))) + if (cls eq defn.UnitClass) constant(tree, unitLiteral) else { assert(cls ne defn.ArrayClass) ref(unboxMethod(cls.asClass)).appliedTo(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala index 03ac15b39ffe..af6533cfc17f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Memoize.scala +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -173,7 +173,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => myState.classesThatNeedReleaseFence += sym.owner val initializer = if isErasableBottomField(field, tree.termParamss.head.head.tpt.tpe.classSymbol) - then Literal(Constant(())) + then unitLiteral else Assign(ref(field), adaptToField(field, ref(tree.termParamss.head.head.symbol))) val setterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(using ctx.withOwner(sym))) sym.keepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index ac1e1868f26e..a648a419d594 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -947,7 +947,7 @@ object PatternMatcher { case LabeledPlan(label, expr) => Labeled(label, emit(expr)) case ReturnPlan(label) => - Return(Literal(Constant(())), ref(label)) + Return(unitLiteral, ref(label)) case plan: SeqPlan => def default = seq(emit(plan.head) :: Nil, emit(plan.tail)) def maybeEmitSwitch(scrutinee: Tree): Tree = { diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 2c552eec6d12..556204ab89ab 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -285,7 +285,7 @@ object TypeTestsCasts { Typed(expr, tree.args.head) // Replace cast by type ascription (which does not generate any bytecode) else if (testCls eq defn.BoxedUnitClass) // as a special case, casting to Unit always successfully returns Unit - Block(expr :: Nil, Literal(Constant(()))).withSpan(expr.span) + Block(expr :: Nil, unitLiteral).withSpan(expr.span) else if (foundClsSymPrimitive) if (testCls.isPrimitiveValueClass) primitiveConversion(expr, testCls) else derivedTree(box(expr), defn.Any_asInstanceOf, testType) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 061d759e9ca4..8b5ac9b2d09c 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1578,7 +1578,7 @@ class RefChecks extends MiniPhase { thisPhase => private def transformIf(tree: If): Tree = { val If(cond, thenpart, elsepart) = tree def unitIfEmpty(t: Tree): Tree = - if (t == EmptyTree) Literal(Constant(())).setPos(tree.pos).setType(UnitTpe) else t + if (t == EmptyTree) unitLiteral.setPos(tree.pos).setType(UnitTpe) else t cond.tpe match { case ConstantType(value) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 54f75d37b2bf..55d755d4e7b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4216,7 +4216,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if (!ctx.isAfterTyper && !tree.isInstanceOf[Inlined] && ctx.settings.WvalueDiscard.value && !isThisTypeResult(tree)) { report.warning(ValueDiscarding(tree.tpe), tree.srcPos) } - return tpd.Block(tree1 :: Nil, Literal(Constant(()))) + return tpd.Block(tree1 :: Nil, unitLiteral) } // convert function literal to SAM closure diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index 70d3c72500b2..29119c8d081f 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -1682,6 +1682,25 @@ class DottyBytecodeTests extends DottyBytecodeTest { assertSameCode(instructions, expected) } } + + @Test def i18320(): Unit = { + val c1 = + """class C { + | def m: Unit = { + | val x = 1 + | } + |} + |""".stripMargin + checkBCode(c1) {dir => + val clsIn = dir.lookupName("C.class", directory = false).input + val clsNode = loadClassNode(clsIn, skipDebugInfo = false) + val method = getMethod(clsNode, "m") + val instructions = instructionsFromMethod(method).filter(_.isInstanceOf[LineNumber]) + val expected = List(LineNumber(3, Label(0))) + assertSameCode(instructions, expected) + + } + } } object invocationReceiversTestCode {