From b08576018168fb6429f1f133502bdda2c6f785d2 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 19 Apr 2025 08:13:09 -0700 Subject: [PATCH] Check inline expansion for exclusion Preserve attachments of literal constant in `42: Unit`. --- .../src/dotty/tools/dotc/typer/Typer.scala | 53 +++++++++++-------- tests/warn/i23018.scala | 19 +++++++ tests/warn/i23250.scala | 4 +- 3 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 tests/warn/i23018.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f2e7e4a2c07c..5a9e28d461de 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1157,7 +1157,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer record("typedNumber") val digits = tree.digits val target = pt.dealias - def lit(value: Any) = Literal(Constant(value)).withSpan(tree.span) + def lit(value: Any) = Literal(Constant(value)).withSpan(tree.span).withAttachmentsFrom(tree) try { // Special case primitive numeric types if (target.isRef(defn.IntClass) || @@ -1207,7 +1207,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } var app: untpd.Tree = untpd.Apply(fromDigits, firstArg :: otherArgs) if (ctx.mode.is(Mode.Pattern)) app = untpd.Block(Nil, app) - return typed(app, pt) + return typed(app, pt).withAttachmentsFrom(tree) case _ => } // Otherwise convert to Int or Double according to digits format @@ -3994,7 +3994,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer traverse(xtree :: rest) case stat :: rest => val stat1 = typed(stat)(using ctx.exprContext(stat, exprOwner)) - if !Linter.warnOnInterestingResultInStatement(stat1) then checkStatementPurity(stat1)(stat, exprOwner) + if !Linter.warnOnInterestingResultInStatement(stat1) then + checkStatementPurity(stat1)(stat, exprOwner, isUnitExpr = false) buf += stat1 traverse(rest)(using stat1.nullableContext) case nil => @@ -4892,15 +4893,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // so will take the code path that decides on inlining val tree1 = adapt(tree, WildcardType, locked) checkStatementPurity(tree1)(tree, ctx.owner, isUnitExpr = true) - - if ctx.settings.Whas.valueDiscard - && !ctx.isAfterTyper - && !tree.isInstanceOf[Inlined] - && !isThisTypeResult(tree) - && !isAscribedToUnit(tree) - then - report.warning(ValueDiscarding(tree.tpe), tree.srcPos) - + checkValueDiscard(tree) return tpd.Block(tree1 :: Nil, unitLiteral) end if @@ -5161,11 +5154,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedExpr(cmp, defn.BooleanType) case _ => - private def checkStatementPurity(tree: tpd.Tree)(original: untpd.Tree, exprOwner: Symbol, isUnitExpr: Boolean = false)(using Context): Unit = + private def checkStatementPurity(tree: tpd.Tree)(original: untpd.Tree, exprOwner: Symbol, isUnitExpr: Boolean) + (using Context): Unit = + inline def isPureNotInlinedUnit = tree match + case Inlined(_, Nil, Literal(k)) if k.tag == UnitTag => false // assert(2 + 2 == 4) + case tree => isPureExpr(tree) if !tree.tpe.isErroneous && !ctx.isAfterTyper - && !tree.isInstanceOf[Inlined] - && isPureExpr(tree) + && isPureNotInlinedUnit && !isSelfOrSuperConstrCall(tree) then tree match case closureDef(meth) @@ -5179,13 +5175,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // sometimes we do not have the original anymore and use the transformed tree instead. // But taken together, the two criteria are quite accurate. missingArgs(tree, tree.tpe.widen) - case _ if tree.hasAttachment(AscribedToUnit) => - // The tree was ascribed to `Unit` explicitly to silence the warning. - () - case _ if isUnitExpr => - report.warning(PureUnitExpression(original, tree.tpe), original.srcPos) - case _ => - report.warning(PureExpressionInStatementPosition(original, exprOwner), original.srcPos) + case tree => + val warnable = tree match + case inlined: Inlined => inlined.expansion + case tree => tree + // Check if the tree was ascribed to `Unit` explicitly to silence the warning. + if !isThisTypeResult(warnable) && !isAscribedToUnit(warnable) then + val msg = + if isUnitExpr then + PureUnitExpression(original, warnable.tpe) + else + PureExpressionInStatementPosition(original, exprOwner) + report.warning(msg, original.srcPos) + + private def checkValueDiscard(tree: tpd.Tree)(using Context): Unit = + if ctx.settings.Whas.valueDiscard && !ctx.isAfterTyper then + val warnable = tree match + case inlined: Inlined => inlined.expansion + case tree => tree + if !isThisTypeResult(warnable) && !isAscribedToUnit(warnable) then + report.warning(ValueDiscarding(warnable.tpe), tree.srcPos) /** Types the body Scala 2 macro declaration `def f = macro ` */ protected def typedScala2MacroBody(call: untpd.Tree)(using Context): Tree = diff --git a/tests/warn/i23018.scala b/tests/warn/i23018.scala new file mode 100644 index 000000000000..f23776277493 --- /dev/null +++ b/tests/warn/i23018.scala @@ -0,0 +1,19 @@ +//> using options -Wvalue-discard + +transparent inline def toto: Any = 1 +transparent inline def uhoh = 42: Unit // nowarn +def tata: Unit = toto // warn pure Discard +def hmm: Unit = uhoh +def literally: Unit = 42 // warn pure Discard +def funnily = 42: Unit // nowarn +def impure = ("*" * 42).length +def impurely: Unit = impure // warn impure discard + +def i: Int = ??? +def parenthetically: Int = + () // warn pure + i +transparent inline def reduced = () +def reductively: Int = + reduced // no warn + i diff --git a/tests/warn/i23250.scala b/tests/warn/i23250.scala index ac9183ed1fd1..6ea33b6b0b79 100644 --- a/tests/warn/i23250.scala +++ b/tests/warn/i23250.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all -Werror +//> using options -Wunused:all trait MonadError[F[_], E] type MonadThrow[F[_]] = MonadError[F, Throwable] @@ -10,7 +10,7 @@ trait WriteResult trait MetaStreamsSyntax: extension [F[_]](ms: MetaStreams[F])(using MonadThrow[F]) def setMaxAge(): F[WriteResult] = - summon[MonadThrow[F]] + summon[MonadThrow[F]] // warn pure expr ms.use[WriteResult] def setTruncateBefore(): F[WriteResult] =