From c489b708315dfea0960d3af26b54880b23124a6b 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 | 67 ++++++++++--------- tests/warn/i23018.scala | 19 ++++++ 2 files changed, 56 insertions(+), 30 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 30d3add2529f..9c380c345c5f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -35,7 +35,7 @@ import util.common.* import util.{Property, SimpleIdentityMap, SrcPos} import Applications.{tupleComponentTypes, wrapDefs, defaultArgument} -import collection.mutable +import collection.mutable, mutable.ListBuffer import Implicits.* import util.Stats.record import config.Printers.{gadts, typr} @@ -207,7 +207,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * a reference for `m` is searched. `null` in all other situations. */ def findRef(name: Name, pt: Type, required: FlagSet, excluded: FlagSet, pos: SrcPos, - altImports: mutable.ListBuffer[TermRef] | Null = null)(using Context): Type = { + altImports: ListBuffer[TermRef] | Null = null)(using Context): Type = { val refctx = ctx val noImports = ctx.mode.is(Mode.InPackageClauseName) def suppressErrors = excluded.is(ConstructorProxy) @@ -1124,7 +1124,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) || @@ -1174,7 +1174,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 @@ -3501,7 +3501,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else { val app = typedApply(desugar.binop(l, op, r).withAttachmentsFrom(tree), pt) if op.name.isRightAssocOperatorName && !ctx.mode.is(Mode.QuotedExprPattern) then - val defs = new mutable.ListBuffer[Tree] + val defs = ListBuffer.empty[Tree] def lift(app: Tree): Tree = (app: @unchecked) match case Apply(fn, args) => if (app.tpe.isError) app @@ -3801,7 +3801,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer trees mapconserve (typed(_)) def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(using Context): (List[Tree], Context) = { - val buf = new mutable.ListBuffer[Tree] + val buf = ListBuffer.empty[Tree] var enumContexts: SimpleIdentityMap[Symbol, Context] = SimpleIdentityMap.empty val initialNotNullInfos = ctx.notNullInfos // A map from `enum` symbols to the contexts enclosing their definitions @@ -3845,7 +3845,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 => @@ -4052,7 +4053,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect, tree.nameSpan) def tryExtension(using Context): Tree = - val altImports = new mutable.ListBuffer[TermRef]() + val altImports = ListBuffer.empty[TermRef] findRef(tree.name, WildcardType, ExtensionMethod, EmptyFlags, qual.srcPos, altImports) match case ref: TermRef => def tryExtMethod(ref: TermRef)(using Context) = @@ -4061,7 +4062,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tryExtMethod(ref) else // Try all possible imports and collect successes and failures - val successes, failures = new mutable.ListBuffer[(Tree, TyperState)] + val successes, failures = ListBuffer.empty[(Tree, TyperState)] for alt <- ref :: altImports.toList do val nestedCtx = ctx.fresh.setNewTyperState() val app = tryExtMethod(alt)(using nestedCtx) @@ -4719,22 +4720,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer return readapt(tree.cast(captured)) // drop type if prototype is Unit - if (pt isRef defn.UnitClass) { + if pt.isRef(defn.UnitClass) then // local adaptation makes sure every adapted tree conforms to its pt // 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) - } // convert function literal to SAM closure tree match { @@ -4992,11 +4984,13 @@ 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) @@ -5010,13 +5004,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